├── .gitignore ├── README.md ├── assets ├── Navigation (react-native-maps-navigation).json └── fonts │ └── Navigation.ttf ├── docs ├── CloseButton.md ├── DirectionInputBox.md ├── DirectionsListView.md ├── DurationDistanceLabel.md ├── DurationDistanceView.md ├── ManeuverArrow.md ├── ManeuverLabel.md ├── ManeuverView.md ├── MapViewNavigation.md ├── PositionMarker.md ├── RouteMarker.md ├── RouterPolyline.md ├── TravelModeBox.md └── preview.gif ├── index.js ├── package.json └── src ├── components ├── CloseButton │ ├── index.js │ └── styles.js ├── DirectionsListView │ ├── index.js │ ├── item.js │ └── styles.js ├── DurationDistanceLabel │ ├── index.js │ └── styles.js ├── DurationDistanceView │ ├── index.js │ └── styles.js ├── ManeuverArrow │ ├── index.js │ └── styles.js ├── ManeuverLabel │ ├── index.js │ └── styles.js ├── ManeuverView │ ├── index.js │ └── styles.js ├── MapViewNavigation │ └── index.js ├── PositionMarker │ ├── index.js │ └── styles.js ├── RouteMarker │ ├── index.js │ └── styles.js ├── RoutePolyline │ └── index.js ├── TravelModeBox │ └── index.js └── TravelModeLabel │ ├── index.js │ └── styles.js ├── constants ├── DirectionTypes.js ├── MarkerTypes.js ├── NavigationIcons.js ├── NavigationModes.js ├── PolylineTypes.js ├── PropTypes.js ├── TrapTypes.js └── TravelModes.js ├── modules ├── Directions.js ├── Geocoder.js ├── Simulator.js ├── Tools.js └── Traps.js └── themes └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | .idea/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-maps-navigation 2 | 3 | Enhances React Native Maps with Realtime Navigation. 4 | 5 | `Please note that this module is usable but still under heavy development. Some properties and/or component names might change without notice.` 6 | 7 | ![alt text](https://github.com/flyandi/react-native-maps-navigation/raw/master/docs/preview.gif "react-native-maps-navigation") 8 | 9 | 10 | ## Installation 11 | 12 | **React Native >= 0.49** 13 | 14 | ```bash 15 | yarn add react-native-maps-navigation 16 | ``` 17 | 18 | Make sure you link the module before building: 19 | 20 | ```bash 21 | react-native link react-native-maps-navigation 22 | ``` 23 | 24 | ## Example 25 | 26 | [Head over to the example application to get started right away.](https://github.com/flyandi/react-native-maps-navigation-example) 27 | 28 | The example application uses most components and api modules of this library and gets you started in a useful direction. 29 | 30 | 31 | ## Components 32 | 33 | The library exposes the following modules and components. 34 | 35 | The main component is `MapViewNavigation`: 36 | 37 | [`` Component API](docs/MapViewNavigation.md) 38 | 39 | 40 | The library also ships with various UI components: 41 | 42 | [`` Component API](docs/DirectionInputBox.md) 43 | 44 | [`` Component API](docs/DirectionsListView.md) 45 | 46 | [`` Component API](docs/DurationDistanceLabel.md) 47 | 48 | [`` Component API](docs/DurationDistanceView.md) 49 | 50 | [`` Component API](docs/ManeuverView.md) 51 | 52 | [`` Component API](docs/ManeuverLabel.md) 53 | 54 | [`` Component API](docs/TravelModeBox.md) 55 | 56 | These are internal components used by the library: 57 | 58 | [`` Component API](docs/ManeuverArrow.md) 59 | 60 | [`` Component API](docs/CloseButton.md) 61 | 62 | [`` Component API](docs/PositionMarker.md) 63 | 64 | [`` Component API](docs/RouteMarker.md) 65 | 66 | [`` Component API](docs/RouterPolyline.md) 67 | 68 | ## General Usage 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /assets/fonts/Navigation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/assets/fonts/Navigation.ttf -------------------------------------------------------------------------------- /docs/CloseButton.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/CloseButton.md -------------------------------------------------------------------------------- /docs/DirectionInputBox.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/DirectionInputBox.md -------------------------------------------------------------------------------- /docs/DirectionsListView.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/DirectionsListView.md -------------------------------------------------------------------------------- /docs/DurationDistanceLabel.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/DurationDistanceLabel.md -------------------------------------------------------------------------------- /docs/DurationDistanceView.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/DurationDistanceView.md -------------------------------------------------------------------------------- /docs/ManeuverArrow.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/ManeuverArrow.md -------------------------------------------------------------------------------- /docs/ManeuverLabel.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/ManeuverLabel.md -------------------------------------------------------------------------------- /docs/ManeuverView.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/ManeuverView.md -------------------------------------------------------------------------------- /docs/MapViewNavigation.md: -------------------------------------------------------------------------------- 1 | # `` Component API 2 | 3 | `MapViewNavigation` is the primary component in react-native-maps-navigation that contains the navigation manager. 4 | 5 | ## Props 6 | 7 | | Prop | Type | Default | Note | 8 | |---|---|---|---| 9 | | `apiKey` | `string` | (Required) | A Google API key that has the Direction API enabled. See below for more information. 10 | | `language` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 11 | | `map` | `function` | | A function that is called to obtain the map instances. 12 | | `origin` | `any` | | A string or object that identifies the origin 13 | | `destination` | `any` | | A string or object that identifies the destination 14 | | `maxZoom` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 15 | | `minZoom` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 16 | | `animationDuration` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 17 | | `navigationMode` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 18 | | `navigationViewingAngle` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 19 | | `navigationZoomLevel` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 20 | | `directionZoomQuantifier` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 21 | | `routeStepDistance` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 22 | | `routeStepInnerTolerance` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 23 | | `routeStepCenterTolerance` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 24 | | `routeStepCourseTolerance` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 25 | | `displayDebugMarkers` | `string` | | A language identifier passed to various submodules. This is useful to show directions in the specific countries language. 26 | 27 | ## Events 28 | 29 | | Prop | Type | Default | Note | 30 | |---|---|---|---| 31 | | `provider` | `string` | | The map framework to use.

Either `"google"` for GoogleMaps, otherwise `null` or `undefined` to use the native map framework (`MapKit` in iOS and `GoogleMaps` in android). 32 | 33 | 34 | 35 | ## Methods 36 | 37 | | Method Name | Arguments | Notes 38 | |---|---|---| 39 | | `navigateRoute` | `location: LatLng`, `bearing: Number`, `angle: Number`, `duration: Number` | Navigates the route -------------------------------------------------------------------------------- /docs/PositionMarker.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/PositionMarker.md -------------------------------------------------------------------------------- /docs/RouteMarker.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/RouteMarker.md -------------------------------------------------------------------------------- /docs/RouterPolyline.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/RouterPolyline.md -------------------------------------------------------------------------------- /docs/TravelModeBox.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/TravelModeBox.md -------------------------------------------------------------------------------- /docs/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flyandi/react-native-maps-navigation/15e0133e46971f57e29d2aadfe6d7bd6fb10c2cb/docs/preview.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import TravelModeBox from './src/components/TravelModeBox'; 5 | import TravelModeLabel from './src/components/TravelModeLabel'; 6 | import DirectionsListView from './src/components/DirectionsListView'; 7 | import MapViewNavigation from './src/components/MapViewNavigation'; 8 | import ManeuverView from './src/components/ManeuverView'; 9 | import ManeuverArrow from './src/components/ManeuverArrow'; 10 | import ManeuverLabel from './src/components/ManeuverLabel'; 11 | import DurationDistanceView from './src/components/DurationDistanceView'; 12 | import DurationDistanceLabel from './src/components/DurationDistanceLabel'; 13 | import TravelIcons from './src/constants/NavigationIcons'; 14 | import TravelModes from './src/constants/TravelModes'; 15 | import NavigationModes from './src/constants/NavigationModes'; 16 | 17 | import Geocoder from './src/modules/Geocoder'; 18 | 19 | 20 | 21 | 22 | 23 | /** 24 | * @exports 25 | */ 26 | export { 27 | DirectionsListView, 28 | ManeuverView, 29 | ManeuverArrow, 30 | ManeuverLabel, 31 | DurationDistanceView, 32 | DurationDistanceLabel, 33 | TravelModeBox, 34 | TravelModes, 35 | TravelIcons, 36 | TravelModeLabel, 37 | Geocoder, 38 | NavigationModes, 39 | }; 40 | 41 | /** 42 | * @default export 43 | */ 44 | export default MapViewNavigation; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-maps-navigation", 3 | "version": "0.0.14", 4 | "description": "A helper and component library for react-native-maps that enables navigation support", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/flyandi/react-native-maps-navigation.git" 9 | }, 10 | "keywords": [ 11 | "react", 12 | "native", 13 | "react-native", 14 | "react-native-maps", 15 | "maps", 16 | "navigation", 17 | "gps", 18 | "ios", 19 | "android", 20 | "react-component" 21 | ], 22 | "author": "Andy Schwarz (http://flyandi.net)", 23 | "license": { 24 | "type": "MIT", 25 | "url": "https://github.com/flyandi/react-native-maps-navigation/blob/master/LICENSE" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/flyandi/react-native-maps-navigation/issues" 29 | }, 30 | "scripts": { 31 | "test": "jest" 32 | }, 33 | "jest": { 34 | "preset": "react-native" 35 | }, 36 | "homepage": "https://github.com/flyandi/react-native-maps-navigation#readme", 37 | "peerDependencies": { 38 | "react": "*", 39 | "react-native": "*", 40 | "react-native-maps": ">=0.21.0" 41 | }, 42 | "dependencies": { 43 | "geolib": "^3.0.2", 44 | "prop-types": "^15.6.2", 45 | "react-native-optiongroup": "^0.0.7" 46 | }, 47 | "rnpm": { 48 | "assets": [ 49 | "./assets/fonts" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/components/CloseButton/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { Text, TouchableOpacity } from 'react-native'; 7 | import Styles from './styles'; 8 | import NavigationIcons from "../../constants/NavigationIcons"; 9 | import {DEFAULT_DIRECTION_TYPE} from "../../constants/DirectionTypes"; 10 | 11 | 12 | /** 13 | * @component 14 | */ 15 | export default class CloseButton extends Component { 16 | 17 | /** 18 | * propTypes 19 | * @type {} 20 | */ 21 | static propTypes = { 22 | maneuver: PropTypes.object, 23 | size: PropTypes.number, 24 | opacity: PropTypes.number, 25 | color: PropTypes.any, 26 | } 27 | 28 | /** 29 | * defaultProps 30 | * @type {} 31 | */ 32 | static defaultProps = { 33 | maneuver: undefined, 34 | size: 25, 35 | opacity: 1, 36 | color: '#000000', 37 | } 38 | 39 | 40 | /** 41 | * @constructor 42 | * @param props 43 | */ 44 | constructor(props) 45 | { 46 | super(props); 47 | } 48 | 49 | 50 | /** 51 | * render 52 | * @returns {XML} 53 | */ 54 | render() 55 | { 56 | const styles = Styles(this.props); 57 | 58 | return ( 59 | 60 | 61 | {NavigationIcons.close} 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/components/CloseButton/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { IconFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @maneuverView 15 | */ 16 | closeButtonText: { 17 | fontFamily: 'Navigation', 18 | fontSize: props.size, 19 | color: props.color, 20 | opacity: props.opacity, 21 | textAlign: 'center', 22 | } 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/DirectionsListView/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { ScrollView, View, TouchableOpacity, Text } from 'react-native'; 7 | import Styles from './styles'; 8 | import DirectionListViewItem from './item'; 9 | 10 | /** 11 | * @component 12 | */ 13 | export default class DirectionsListView extends Component { 14 | 15 | /** 16 | * propTypes 17 | * @type {} 18 | */ 19 | static propTypes = { 20 | route: PropTypes.any.isRequired, 21 | fontFamily: PropTypes.string, 22 | fontFamilyBold: PropTypes.string, 23 | showOriginDestinationHeader: PropTypes.bool, 24 | displayTravelMode: PropTypes.bool, 25 | } 26 | 27 | /** 28 | * defaultProps 29 | * @type {} 30 | */ 31 | static defaultProps = { 32 | route: undefined, 33 | fontFamily: undefined, 34 | fontFamilyBold: undefined, 35 | showOriginDestinationHeader: true, 36 | displayTravelMode: false, 37 | } 38 | 39 | 40 | /** 41 | * @constructor 42 | * @param props 43 | */ 44 | constructor(props) 45 | { 46 | super(props); 47 | 48 | } 49 | 50 | 51 | /** 52 | * render 53 | * @returns {XML} 54 | */ 55 | render() 56 | { 57 | let index = 0; 58 | 59 | const steps = this.props.route ? this.props.route.steps : false; 60 | 61 | if(steps.constructor !== Array) return null; 62 | 63 | const styles = Styles(this.props); 64 | 65 | return ( 66 | 67 | 68 | {!this.props.showOriginDestinationHeader ? null: ( 69 | 70 | 71 | 72 | FROM 73 | 74 | 75 | {this.props.route.origin.address} 76 | 77 | 78 | 79 | 80 | TO 81 | 82 | 83 | {this.props.route.destination.address} 84 | 85 | 86 | 87 | )} 88 | 89 | 90 | {this.props.route.duration.text} 91 | {this.props.route.distance.text} 92 | 93 | 94 | 95 | {steps.map((step, index) => )} 96 | 97 | 98 | ); 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /src/components/DirectionsListView/item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { View, TouchableOpacity, Text } from 'react-native'; 7 | import Styles from './styles'; 8 | import ManeuverArrow from '../ManeuverArrow'; 9 | import ManeuverLabel from '../ManeuverLabel'; 10 | import DurationDistanceLabel from '../DurationDistanceLabel'; 11 | 12 | /** 13 | * @component 14 | */ 15 | export default class DirectionListViewItem extends Component { 16 | 17 | /** 18 | * propTypes 19 | * @type {} 20 | */ 21 | static propTypes = { 22 | instructions: PropTypes.string, 23 | distance: PropTypes.object, 24 | duration: PropTypes.object, 25 | maneuver: PropTypes.object, 26 | fontFamily: PropTypes.string, 27 | fontFamilyBold: PropTypes.string, 28 | fontSize: PropTypes.number, 29 | displayTravelMode: PropTypes.bool, 30 | } 31 | 32 | /** 33 | * defaultProps 34 | * @type {} 35 | */ 36 | static defaultProps = { 37 | instructions: '', 38 | fontFamily: undefined, 39 | fontFamilyBold: undefined, 40 | distance: undefined, 41 | duration: undefined, 42 | maneuver: undefined, 43 | fontSize: undefined, 44 | displayTravelMode: false, 45 | } 46 | 47 | 48 | /** 49 | * @constructor 50 | * @param props 51 | */ 52 | constructor(props) 53 | { 54 | super(props); 55 | } 56 | 57 | /** 58 | * render 59 | * @returns {XML} 60 | */ 61 | render() 62 | { 63 | const styles = Styles(this.props); 64 | 65 | return ( 66 | 67 | 68 | 72 | 73 | 74 | 77 | 82 | 83 | 84 | ); 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /src/components/DirectionsListView/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { IconFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @directionDetailHeader 15 | */ 16 | directionDetailHeader: { 17 | padding: 25, 18 | paddingBottom: 10, 19 | backgroundColor: '#f7f7f4', 20 | flexDirection: 'column' 21 | }, 22 | 23 | directionDetailHeaderSection: { 24 | marginBottom: 15, 25 | flexDirection: 'column', 26 | }, 27 | 28 | directionDetailHeaderAddressText: { 29 | fontSize: 16, 30 | fontFamily: props.fontFamily, 31 | }, 32 | 33 | directionDetailHeaderAddressLabel: { 34 | fontSize: 13, 35 | opacity: 0.7, 36 | fontWeight: 'bold', 37 | fontFamily: props.fontFamily, 38 | }, 39 | 40 | /** 41 | * @directionDetailTravel 42 | */ 43 | directionDetailTravel: { 44 | margin: 25 45 | }, 46 | 47 | directionDetailTravelDuration: { 48 | fontSize: 32, 49 | fontFamily: props.fontFamily, 50 | color: '#387bc1', 51 | }, 52 | 53 | directionDetailTravelDistance: { 54 | fontSize: 22, 55 | fontFamily: props.fontFamily, 56 | opacity: 0.8 57 | }, 58 | 59 | /** 60 | * @fonts 61 | */ 62 | 63 | bold: { 64 | fontWeight: 'bold', 65 | fontFamily: props.fontFamilyBold || props.fontFamily, 66 | fontSize: 16, 67 | flexWrap: 'wrap', 68 | }, 69 | 70 | regular: { 71 | fontFamily: props.fontFamily, 72 | fontSize: 16, 73 | flexWrap: 'wrap', 74 | }, 75 | 76 | extra: { 77 | fontFamily: props.fontFamily, 78 | fontSize: 13, 79 | flexWrap: 'wrap', 80 | color: '#387bc1', 81 | }, 82 | 83 | durationDistance: { 84 | fontFamily: props.fontFamily, 85 | fontSize: 14, 86 | opacity: 0.8, 87 | flexWrap: 'wrap', 88 | }, 89 | 90 | /** 91 | * @directionDetail 92 | */ 93 | 94 | directionDetailSectionContainer: { 95 | margin: 25, 96 | marginTop: 0, 97 | flex: 1 98 | }, 99 | 100 | directionDetailSection: { 101 | borderColor: '#e6e6e6', 102 | borderTopWidth: 1, 103 | paddingTop: 20, 104 | marginBottom: 20, 105 | flexDirection: 'row', 106 | flex: 1, 107 | }, 108 | 109 | directionDetailIconContainer: { 110 | width: 50, 111 | flex: 0, 112 | justifyContent: 'flex-start', 113 | alignItems: 'center' 114 | }, 115 | 116 | directionDetailContent: { 117 | flexDirection: 'column', 118 | flex: 1, 119 | }, 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /src/components/DurationDistanceLabel/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { View, TouchableOpacity, Text } from 'react-native'; 7 | import Styles from './styles'; 8 | import {MODE_MAPPING} from "../../constants/TravelModes"; 9 | 10 | /** 11 | * @component 12 | */ 13 | export default class DurationDistanceLabel extends Component { 14 | 15 | /** 16 | * propTypes 17 | * @type {} 18 | */ 19 | static propTypes = { 20 | style: PropTypes.any, 21 | instructions: PropTypes.string, 22 | fontFamily: PropTypes.string, 23 | fontSize: PropTypes.number, 24 | distance: PropTypes.object, 25 | duration: PropTypes.object, 26 | opacity: PropTypes.number, 27 | withTravelModeIcon: PropTypes.bool, 28 | } 29 | 30 | /** 31 | * defaultProps 32 | * @type {} 33 | */ 34 | static defaultProps = { 35 | style: {}, 36 | fontFamily: undefined, 37 | fontSize: 16, 38 | distance: undefined, 39 | duration: undefined, 40 | opacity: 0.8, 41 | withTravelModeIcon: false, 42 | } 43 | 44 | 45 | /** 46 | * @constructor 47 | * @param props 48 | */ 49 | constructor(props) 50 | { 51 | super(props); 52 | } 53 | 54 | /** 55 | * render 56 | * @returns {XML} 57 | */ 58 | render() 59 | { 60 | const styles = Styles(this.props); 61 | 62 | const travelMode = MODE_MAPPING[this.props.mode]; 63 | 64 | return ( 65 | 66 | {!this.props.withTravelModeIcon || !travelMode ? null : ( 67 | {travelMode.icon}{' '} 68 | )} 69 | {this.props.distance ? this.props.distance.text : ''} 70 | {this.props.duration ? [' (', this.props.duration.text, ')'].join("") : ''} 71 | 72 | ); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/components/DurationDistanceLabel/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { NavigationIconsFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @durationDistanceText 15 | */ 16 | durationDistanceText: { 17 | fontFamily: props.fontFamily, 18 | fontSize: props.fontSize * 0.8, 19 | opacity: props.opacity, 20 | flexWrap: 'wrap', 21 | }, 22 | 23 | durationDistanceTravelModeIcon: { 24 | ...NavigationIconsFont, 25 | fontSize: props.fontSize * 0.8, 26 | opacity: props.opacity, 27 | marginRight: 8, 28 | }, 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/DurationDistanceView/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { ScrollView, View, TouchableOpacity, Text } from 'react-native'; 7 | import Styles from './styles'; 8 | import CloseButton from "../CloseButton"; 9 | import DurationDistanceLabel from "../DurationDistanceLabel"; 10 | 11 | 12 | /** 13 | * @component 14 | */ 15 | export default class ManeuverView extends Component { 16 | 17 | /** 18 | * propTypes 19 | * @type {} 20 | */ 21 | static propTypes = { 22 | step: PropTypes.any.isRequired, 23 | fontFamily: PropTypes.string, 24 | fontFamilyBold: PropTypes.string, 25 | fontSize: PropTypes.number, 26 | arrowSize: PropTypes.number, 27 | arrowColor: PropTypes.string, 28 | withCloseButton: PropTypes.bool, 29 | onClose: PropTypes.func, 30 | onPress: PropTypes.func, 31 | } 32 | 33 | /** 34 | * defaultProps 35 | * @type {} 36 | */ 37 | static defaultProps = { 38 | step: undefined, 39 | fontFamily: undefined, 40 | fontFamilyBold: undefined, 41 | fontSize: 20, 42 | arrowSize: 50, 43 | arrowColor: '#545455', 44 | withCloseButton: false, 45 | onClose: undefined, 46 | onPress: undefined, 47 | } 48 | 49 | 50 | /** 51 | * @constructor 52 | * @param props 53 | */ 54 | constructor(props) 55 | { 56 | super(props); 57 | 58 | } 59 | 60 | 61 | /** 62 | * render 63 | * @returns {XML} 64 | */ 65 | render() 66 | { 67 | const styles = Styles(this.props); 68 | 69 | const step = this.props.step; 70 | 71 | if(!step) return null; 72 | 73 | return ( 74 | 75 | 76 | 77 | 78 | {step.distance ? step.distance.text : ''} 79 | 80 | 81 | 82 | {step.duration ? step.duration.text : ''} 83 | 84 | 85 | 86 | {!this.props.withCloseButton ? null : ( 87 | 88 | this.props.onClose && this.props.onClose()} /> 89 | 90 | )} 91 | 92 | ); 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/components/DurationDistanceView/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { IconFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @maneuverView 15 | */ 16 | durationDistanceView: { 17 | padding: 15, 18 | backgroundColor: '#f7f7f4', 19 | flexDirection: 'row', 20 | minHeight: 120, 21 | alignItems: 'center', 22 | }, 23 | 24 | durationDistanceContent: { 25 | flex: 1, 26 | }, 27 | 28 | durationDistanceClose: { 29 | flex: 0, 30 | width: 30, 31 | justifyContent: 'flex-end', 32 | alignItems: 'flex-end', 33 | } 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/ManeuverArrow/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { Text } from 'react-native'; 7 | import Styles from './styles'; 8 | import NavigationIcons from "../../constants/NavigationIcons"; 9 | import {DEFAULT_DIRECTION_TYPE} from "../../constants/DirectionTypes"; 10 | 11 | 12 | /** 13 | * @component 14 | */ 15 | export default class ManeuverArrow extends Component { 16 | 17 | /** 18 | * propTypes 19 | * @type {} 20 | */ 21 | static propTypes = { 22 | maneuver: PropTypes.object, 23 | size: PropTypes.number, 24 | opacity: PropTypes.number, 25 | color: PropTypes.any, 26 | } 27 | 28 | /** 29 | * defaultProps 30 | * @type {} 31 | */ 32 | static defaultProps = { 33 | maneuver: undefined, 34 | size: 25, 35 | opacity: 1, 36 | color: '#000000', 37 | } 38 | 39 | 40 | /** 41 | * @constructor 42 | * @param props 43 | */ 44 | constructor(props) 45 | { 46 | super(props); 47 | } 48 | 49 | 50 | /** 51 | * render 52 | * @returns {XML} 53 | */ 54 | render() 55 | { 56 | const styles = Styles(this.props); 57 | 58 | const icon = this.props.maneuver && (this.props.maneuver.name || DEFAULT_DIRECTION_TYPE); 59 | 60 | return ( 61 | 62 | {NavigationIcons[icon]} 63 | 64 | ); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/components/ManeuverArrow/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { IconFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @maneuverView 15 | */ 16 | maneuverArrow: { 17 | fontFamily: 'Navigation', 18 | fontSize: props.size, 19 | color: props.color, 20 | opacity: props.opacity, 21 | textAlign: 'center', 22 | } 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/ManeuverLabel/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { View, TouchableOpacity, Text } from 'react-native'; 7 | import Styles from './styles'; 8 | 9 | 10 | /** 11 | * @component 12 | */ 13 | export default class ManeuverLabel extends Component { 14 | 15 | /** 16 | * propTypes 17 | * @type {} 18 | */ 19 | static propTypes = { 20 | instructions: PropTypes.string, 21 | fontFamily: PropTypes.string, 22 | fontFamilyBold: PropTypes.string, 23 | fontSize: PropTypes.number, 24 | fontColor: PropTypes.string 25 | } 26 | 27 | /** 28 | * defaultProps 29 | * @type {} 30 | */ 31 | static defaultProps = { 32 | instructions: '', 33 | fontFamily: undefined, 34 | fontFamilyBold: undefined, 35 | fontSize: 15, 36 | fontColor: undefined 37 | } 38 | 39 | /** 40 | * @constructor 41 | * @param props 42 | */ 43 | constructor(props) 44 | { 45 | super(props); 46 | } 47 | 48 | /** 49 | * getParsedInstructions 50 | * @param styles 51 | * @returns {*} 52 | */ 53 | getParsedInstructions(styles) 54 | { 55 | const parts = []; 56 | 57 | const regex = /(\w+)|<(.*?)>(.*?)<\/.*?>/g; 58 | 59 | const mapping = { 60 | r: styles.regular, 61 | b: styles.bold, 62 | d: styles.durationDistance, 63 | div: styles.extra, 64 | }; 65 | 66 | let m; 67 | let last = false; 68 | while((m = regex.exec(this.props.instructions))) { 69 | 70 | if (m.index === regex.lastIndex) { 71 | regex.lastIndex++; 72 | last = true; 73 | } 74 | 75 | if(m[2]) { 76 | let tag = m[2].split(" ")[0]; 77 | 78 | if(tag == "div") m[3] = '\n' + m[3]; 79 | 80 | parts.push({m[3]}{last ? '.' : ' '}); 81 | 82 | } else { 83 | parts.push({m[0]}{last ? '.': ' '}); 84 | } 85 | } 86 | 87 | return ( 88 | 89 | {parts} 90 | 91 | ) 92 | } 93 | 94 | /** 95 | * render 96 | * @returns {XML} 97 | */ 98 | render() 99 | { 100 | const styles = Styles(this.props); 101 | 102 | return ( 103 | 104 | {this.getParsedInstructions(styles)} 105 | 106 | ); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /src/components/ManeuverLabel/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { IconFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @maneuverLabel 15 | */ 16 | maneuverLabel: { 17 | flexDirection: 'row' 18 | }, 19 | 20 | /** 21 | * @fonts 22 | */ 23 | 24 | bold: { 25 | fontWeight: 'bold', 26 | fontFamily: props.fontFamilyBold || props.fontFamily, 27 | fontSize: props.fontSize, 28 | flexWrap: 'wrap', 29 | color: props.color 30 | }, 31 | 32 | regular: { 33 | fontFamily: props.fontFamily, 34 | fontSize: props.fontSize, 35 | flexWrap: 'wrap', 36 | color: props.color 37 | }, 38 | 39 | extra: { 40 | fontFamily: props.fontFamily, 41 | fontSize: props.fontSize * 0.8, 42 | flexWrap: 'wrap', 43 | color: '#387bc1', 44 | marginTop: 4, 45 | }, 46 | 47 | durationDistance: { 48 | fontFamily: props.fontFamily, 49 | fontSize: props.fontSize * 0.8, 50 | opacity: 0.8, 51 | flexWrap: 'wrap', 52 | }, 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /src/components/ManeuverView/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { ScrollView, View, TouchableOpacity, Text } from 'react-native'; 7 | import Styles from './styles'; 8 | import ManeuverArrow from '../ManeuverArrow'; 9 | import ManeuverLabel from '../ManeuverLabel'; 10 | import CloseButton from "../CloseButton"; 11 | 12 | 13 | /** 14 | * @component 15 | */ 16 | export default class ManeuverView extends Component { 17 | 18 | /** 19 | * propTypes 20 | * @type {} 21 | */ 22 | static propTypes = { 23 | step: PropTypes.any.isRequired, 24 | fontFamily: PropTypes.string, 25 | fontFamilyBold: PropTypes.string, 26 | fontSize: PropTypes.number, 27 | arrowSize: PropTypes.number, 28 | arrowColor: PropTypes.string, 29 | backgroundColor: PropTypes.string, 30 | withCloseButton: PropTypes.bool, 31 | onClose: PropTypes.func, 32 | onPress: PropTypes.func, 33 | } 34 | 35 | /** 36 | * defaultProps 37 | * @type {} 38 | */ 39 | static defaultProps = { 40 | step: undefined, 41 | fontFamily: undefined, 42 | fontFamilyBold: undefined, 43 | backgroundColor: '#f7f7f4', 44 | fontSize: 20, 45 | arrowSize: 50, 46 | arrowColor: '#545455', 47 | withCloseButton: false, 48 | onClose: undefined, 49 | onPress: undefined, 50 | } 51 | 52 | 53 | /** 54 | * @constructor 55 | * @param props 56 | */ 57 | constructor(props) 58 | { 59 | super(props); 60 | 61 | } 62 | 63 | 64 | /** 65 | * render 66 | * @returns {XML} 67 | */ 68 | render() 69 | { 70 | const styles = Styles(this.props); 71 | 72 | const step = this.props.step; 73 | 74 | if(!step) return null; 75 | 76 | const maneuver = step.maneuver; 77 | 78 | return ( 79 | 80 | 81 | 86 | 87 | 88 | 94 | 95 | {!this.props.withCloseButton ? null : ( 96 | 97 | this.props.onClose && this.props.onClose()} /> 98 | 99 | )} 100 | 101 | ); 102 | } 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/components/ManeuverView/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { IconFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @maneuverView 15 | */ 16 | maneuverView: { 17 | padding: 15, 18 | backgroundColor: props.backgroundColor , 19 | flexDirection: 'row', 20 | minHeight: 120, 21 | alignItems: 'center', 22 | }, 23 | 24 | maneuverViewArrow: { 25 | flex: 0, 26 | width: 80, 27 | justifyContent: 'center', 28 | alignItems: 'center', 29 | }, 30 | 31 | maneuverViewDirection: { 32 | flex: 1, 33 | }, 34 | 35 | maneuverClose: { 36 | flex: 0, 37 | width: 30, 38 | justifyContent: 'flex-end', 39 | alignItems: 'flex-end', 40 | }, 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /src/components/MapViewNavigation/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import {CoordinatePropType} from '../../constants/PropTypes'; 7 | import { View, TouchableOpacity, Text, Dimensions, Geolocation } from 'react-native'; 8 | import connectTheme from '../../themes'; 9 | import Geocoder from '../../modules/Geocoder'; 10 | import Directions from '../../modules/Directions'; 11 | import TravelModes from '../../constants/TravelModes'; 12 | import NavigationModes from '../../constants/NavigationModes'; 13 | import * as Tools from '../../modules/Tools'; 14 | import Simulator from '../../modules/Simulator'; 15 | import Traps from '../../modules/Traps'; 16 | import RouteMarker from '../RouteMarker'; 17 | import RoutePolyline from '../RoutePolyline'; 18 | import PositionMarker from '../PositionMarker'; 19 | import {POSITION_ARROW} from "../../constants/MarkerTypes"; 20 | import {Circle, Polygon, Polyline} from 'react-native-maps'; 21 | 22 | 23 | /** 24 | * @component 25 | */ 26 | export default class MapViewNavigation extends Component { 27 | 28 | /** 29 | * propTypes 30 | * @type {} 31 | */ 32 | static propTypes = { 33 | origin: PropTypes.oneOfType([PropTypes.string, CoordinatePropType, PropTypes.bool]), 34 | destination: PropTypes.oneOfType([PropTypes.string, CoordinatePropType, PropTypes.bool]), 35 | apiKey: PropTypes.string.isRequired, 36 | language: PropTypes.string, 37 | map: PropTypes.func, 38 | navigationMode: PropTypes.string, 39 | travelMode: PropTypes.string, 40 | maxZoom: PropTypes.number, 41 | minZoom: PropTypes.number, 42 | animationDuration: PropTypes.number, 43 | navigationMode: PropTypes.string, 44 | navigationViewingAngle: PropTypes.number, 45 | navigationZoomLevel: PropTypes.number, 46 | directionZoomQuantifier: PropTypes.number, 47 | onRouteChange: PropTypes.func, 48 | onStepChange: PropTypes.func, 49 | onNavigationStarted: PropTypes.func, 50 | onNavigationCompleted: PropTypes.func, 51 | routeStepDistance: PropTypes.number, 52 | routeStepInnerTolerance: PropTypes.number, 53 | routeStepCenterTolerance: PropTypes.number, 54 | routeStepCourseTolerance: PropTypes.number, 55 | displayDebugMarkers: PropTypes.bool, 56 | simulate: PropTypes.bool, 57 | options: PropTypes.object 58 | } 59 | 60 | /** 61 | * defaultProps 62 | * @type {} 63 | */ 64 | static defaultProps = { 65 | origin: false, 66 | destination: false, 67 | apiKey: undefined, 68 | language: undefined, 69 | map: undefined, 70 | navigationMode: NavigationModes.IDLE, 71 | travelMode: TravelModes.DRIVING, 72 | maxZoom: 21, 73 | minZoom: 5, 74 | animationDuration: 750, 75 | navigationViewingAngle: 60, 76 | navigationZoomLevel: 14, 77 | directionZoomQuantifier: 1.5, 78 | onRouteChange: undefined, 79 | onStepChange: undefined, 80 | onNavigationStarted: undefined, 81 | onNavigationCompleted: undefined, 82 | routeStepDistance: 15, 83 | routeStepInnerTolerance: 0.75, 84 | routeStepCenterTolerance: 0.1, 85 | routeStepCourseTolerance: 30, // in degress 86 | displayDebugMarkers: false, 87 | simulate: false, 88 | options: {} 89 | } 90 | 91 | /** 92 | * @constructor 93 | * @param props 94 | */ 95 | constructor(props) { 96 | super(props); 97 | 98 | this.geoCoder = new Geocoder(this.props.apiKey, { 99 | language: this.props.language 100 | }); 101 | 102 | this.directionsCoder = new Directions(this.props.apiKey, { 103 | language: this.props.language 104 | }); 105 | 106 | 107 | 108 | this.traps = new Traps(this); 109 | 110 | this.state = { 111 | route: false, 112 | markers: [], 113 | position: {}, 114 | navigationMode: NavigationModes.IDLE, 115 | travelMode: TravelModes.DRIVING, 116 | stepIndex: false, 117 | }; 118 | 119 | this.theme = connectTheme(this.props.theme); 120 | 121 | const {width, height} = Dimensions.get('window'); 122 | 123 | this.aspectRatio = width / height; 124 | } 125 | 126 | /** 127 | * @componentDidMount 128 | */ 129 | componentDidMount() 130 | { 131 | this.watchId = navigator.geolocation.watchPosition(position => { 132 | 133 | this.setPosition(position.coords); 134 | 135 | }); 136 | } 137 | 138 | /** 139 | * @componentWillUnmount 140 | */ 141 | componentWillUnmount() 142 | { 143 | navigator.geolocation.clearWatch(this.watchId); 144 | } 145 | 146 | /** 147 | * @componentDidUpdate 148 | * @param prevProps 149 | * @param prevState 150 | */ 151 | componentDidUpdate(prevProps, prevState) 152 | { 153 | if(this.props.origin && this.props.destination) { 154 | 155 | if( 156 | (prevProps.navigationMode != this.props.navigationMode) || 157 | (prevProps.travelMode != this.props.travelMode) || 158 | (prevProps.origin != this.props.origin || prevProps.destination != this.props.destination) 159 | ) { 160 | this.updateRoute(); 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * getCoordinates 167 | * @param address 168 | * @param raw 169 | * @returns {Promise} 170 | */ 171 | getCoordinates(address, raw = false) { 172 | return this.geoCoder.getFromLocation(address).then(results => { 173 | 174 | let coordinates = raw ? results : this.geoCoder.minimizeResults(results); 175 | 176 | return coordinates.length == 1 ? coordinates[0] : coordinates; 177 | }); 178 | } 179 | 180 | /** 181 | * getZoomValue 182 | * @param level 183 | */ 184 | getZoomValue(level) { 185 | const value = 0.00001 * (this.props.maxZoom - (level < this.props.minZoom ? this.props.minZoom : level)); 186 | 187 | return { 188 | latitudeDelta: value, 189 | longitudeDelta: value * this.aspectRatio 190 | } 191 | } 192 | 193 | /** 194 | * getBoundingBoxZoomValue 195 | * @param b 196 | * @param quantifier 197 | * @returns {*} 198 | */ 199 | getBoundingBoxZoomValue(b, quantifier = 1) { 200 | 201 | if(b.length != 2) return {}; 202 | 203 | const latitudeDelta = (b[0].latitude > b[1].latitude ? b[0].latitude - b[1].latitude : b[1].latitude - b[0].latitude) * quantifier; 204 | 205 | return { 206 | latitudeDelta, 207 | longitudeDelta: latitudeDelta * this.aspectRatio, 208 | }; 209 | } 210 | 211 | /** 212 | * updatePosition 213 | * @param coordinate 214 | * @param duration 215 | */ 216 | updatePosition(coordinate, duration = 0) 217 | { 218 | this.props.map().animateToCoordinate(coordinate, duration); 219 | } 220 | 221 | /** 222 | * updateBearing 223 | * @param bearing 224 | * @param duration 225 | */ 226 | updateBearing(bearing, duration = false) 227 | { 228 | this.props.map().animateToBearing(bearing, duration || this.props.animationDuration); 229 | } 230 | 231 | /** 232 | * 233 | * @param stepIndex 234 | */ 235 | updateStep(stepIndex = 0) 236 | { 237 | const step = this.state.route.steps[stepIndex < 0 ? 0 : stepIndex]; 238 | 239 | const nextStep = this.state.route.steps[stepIndex + 1]; 240 | 241 | this.props.onStepChange && this.props.onStepChange(step, nextStep); 242 | 243 | this.traps.watchStep(step, nextStep, { 244 | distance: this.props.routeStepDistance, 245 | innerRadiusTolerance: this.props.routeStepInnerTolerance, 246 | centerRadiusTolerance: this.props.routeStepCenterTolerance, 247 | courseTolerance: this.props.routeStepCourseTolerance, 248 | }, (trap, event, state) => { 249 | 250 | if(!nextStep && trap.isCenter()) { 251 | 252 | this.props.onNavigationCompleted && this.props.onNavigationCompleted(); 253 | 254 | return this.setState({ 255 | navigationMode: NavigationModes.IDLE, 256 | stepIndex: false 257 | }); 258 | } 259 | 260 | if(trap.isLeaving()) { 261 | this.updateStep(this.stepIndex); 262 | } 263 | }); 264 | 265 | this.stepIndex = stepIndex + 1; // ensures that this is a real number 266 | } 267 | 268 | /** 269 | * setPosition 270 | * @param position 271 | */ 272 | setPosition(position) 273 | { 274 | const {latitude, longitude, heading} = position; 275 | 276 | position.coordinate = {latitude, longitude}; 277 | 278 | // process traps on setPosition 279 | this.traps.execute(position); 280 | 281 | // update position on map 282 | if(this.state.navigationMode == NavigationModes.NAVIGATION) { 283 | 284 | this.updatePosition(position); 285 | 286 | this.updateBearing(heading); 287 | } 288 | 289 | this.setState({position}); 290 | } 291 | 292 | /** 293 | * clearRoute 294 | * @void 295 | */ 296 | clearRoute() 297 | { 298 | this.setState({route: false, step: false, stepIndex: false}) 299 | } 300 | 301 | /** 302 | * updateRoute 303 | * @param origin 304 | * @param destination 305 | * @param navigationMode 306 | */ 307 | updateRoute(origin = false, destination = false, navigationMode = false, options = null) 308 | { 309 | origin = origin || this.props.origin; 310 | destination = destination || this.props.destination; 311 | navigationMode = navigationMode || this.props.navigationMode; 312 | options = options || this.props.options 313 | 314 | switch(navigationMode) { 315 | 316 | case NavigationModes.ROUTE: 317 | this.displayRoute(origin, destination, options); 318 | break; 319 | 320 | case NavigationModes.NAVIGATION: 321 | this.navigateRoute(origin, destination, options); 322 | break; 323 | } 324 | } 325 | 326 | /** 327 | * Prepares the route 328 | * @param origin 329 | * @param destination 330 | * @param mode 331 | * @param options 332 | * @returns {PromiseLike | Promise} 333 | */ 334 | prepareRoute(origin, destination, options = false, testForRoute = false) 335 | { 336 | if(testForRoute && this.state.route) { 337 | return Promise.resolve(this.state.route); 338 | } 339 | options = Object.assign({}, {mode: this.state.travelMode}, {mode: this.props.travelMode}, options.constructor == Object ? options : {}); 340 | 341 | return this.directionsCoder.fetch(origin, destination, options).then(routes => { 342 | 343 | if(routes.length) { 344 | 345 | const route = routes[0]; 346 | 347 | this.props.onRouteChange && this.props.onRouteChange(route); 348 | 349 | this.props.onStepChange && this.props.onStepChange(false); 350 | 351 | this.setState({route, step: false}); 352 | 353 | return Promise.resolve(route); 354 | } 355 | 356 | return Promise.reject(); 357 | 358 | }); 359 | } 360 | 361 | /** 362 | * displayRoute 363 | * @param origin 364 | * @param destination 365 | * @param options 366 | * @returns {PromiseLike | Promise} 367 | */ 368 | displayRoute(origin, destination, options = false) 369 | { 370 | return this.prepareRoute(origin, destination, options).then(route => { 371 | 372 | const region = { 373 | ...route.bounds.center, 374 | ...this.getBoundingBoxZoomValue(route.bounds.boundingBox, this.props.directionZoomQuantifier) 375 | } 376 | 377 | this.props.map().animateToRegion(region, this.props.animationDuration); 378 | 379 | if(!this.state.navigationMode == NavigationModes.ROUTE) { 380 | this.setState({ 381 | navigationMode: NavigationModes.ROUTE, 382 | }); 383 | } 384 | 385 | return Promise.resolve(route); 386 | }).catch((err) => console.log(err)); 387 | } 388 | 389 | /** 390 | * navigateRoute 391 | * @param origin 392 | * @param destination 393 | * @param options 394 | * @returns {PromiseLike | Promise} 395 | */ 396 | navigateRoute(origin, destination, options = false) 397 | { 398 | return this.prepareRoute(origin, destination, options, true).then(route => { 399 | 400 | const region = { 401 | ...route.origin.coordinate, 402 | ...this.getZoomValue(this.props.navigationZoomLevel), 403 | }; 404 | 405 | this.props.map().animateToRegion(region, this.props.animationDuration); 406 | this.props.map().animateToViewingAngle(this.props.navigationViewingAngle, this.props.animationDuration); 407 | 408 | //this.updatePosition(route.origin.coordinate); 409 | this.updateBearing(route.initialBearing); 410 | 411 | this.setState({ 412 | navigationMode: NavigationModes.NAVIGATION, 413 | }); 414 | 415 | this.updateStep(0); 416 | 417 | this.props.onNavigationStarted && this.props.onNavigationStarted(); 418 | 419 | if (this.props.simulate) { 420 | console.log("SIMULATING ROUTE") 421 | this.simulator = new Simulator(this); 422 | setTimeout(() => this.simulator.start(route), this.props.animationDuration * 1.5); 423 | } else { 424 | console.log("NOT SIMULATING") 425 | } 426 | 427 | return Promise.resolve(route); 428 | }); 429 | } 430 | 431 | /** 432 | * getRouteMarkers 433 | * @param route 434 | * @returns {*} 435 | */ 436 | getRouteMarkers(route) 437 | { 438 | if (!route || route.markers.constructor !== Array) return null; 439 | 440 | return route.markers.map((params, index) => { 441 | 442 | return ( 443 | 448 | ); 449 | }); 450 | } 451 | 452 | /** 453 | * getPositionMarker 454 | * @param position 455 | * @param navigationMode 456 | * @returns {*} 457 | */ 458 | getPositionMarker(position, navigationMode) 459 | { 460 | const type = navigationMode == NavigationModes.NAVIGATION ? POSITION_ARROW : undefined; 461 | 462 | return ( 463 | 469 | ) 470 | } 471 | 472 | /** 473 | * Route Polycons 474 | * @param route 475 | * @returns {*} 476 | */ 477 | getRoutePolylines(route) 478 | { 479 | if (!route || route.polylines.constructor !== Array) return null; 480 | 481 | return route.polylines.map((params, index) => { 482 | 483 | return params ? ( 484 | 489 | ) : null; 490 | }); 491 | } 492 | 493 | /** 494 | * getDebugShapes 495 | * @param route 496 | * @returns {Array} 497 | */ 498 | getDebugShapes(route) 499 | { 500 | let result = []; 501 | 502 | if(!route || !this.props.displayDebugMarkers) return result; 503 | 504 | 505 | const steps = this.state.route.steps; 506 | 507 | let c = 0; 508 | 509 | steps.forEach((step, index) => { 510 | 511 | const coordinate = step.start; 512 | 513 | [ 514 | {radius: this.props.routeStepDistance, color: 'blue'}, 515 | {radius: this.props.routeStepDistance * this.props.routeStepInnerTolerance, color: 'red'}, 516 | {radius: this.props.routeStepDistance * this.props.routeStepCenterTolerance, color: 'green'} 517 | ].forEach(d => { 518 | result.push(); 519 | c++; 520 | }); 521 | 522 | [ 523 | {radius: this.props.routeStepDistance, color: 'blue'} 524 | ].forEach(d => { 525 | 526 | let bearing = step.bearing; // - 180 > 0 ? step.bearing - 180 : 360 - step.bearing - 180; 527 | 528 | let coords = Tools.toArcPolygon( 529 | coordinate, 530 | bearing - this.props.routeStepCourseTolerance, 531 | bearing + this.props.routeStepCourseTolerance, 532 | this.props.routeStepDistance 533 | ) 534 | 535 | result.push(); 536 | c++; 537 | }) 538 | 539 | 540 | 541 | }); 542 | 543 | 544 | return result; 545 | } 546 | 547 | 548 | 549 | /** 550 | * @render 551 | * @returns {*[]} 552 | */ 553 | render() 554 | { 555 | const result = [ 556 | this.getRouteMarkers(this.state.route), 557 | this.getRoutePolylines(this.state.route), 558 | this.getPositionMarker(this.state.position, this.state.navigationMode), 559 | this.getDebugShapes(this.state.route) 560 | ]; 561 | 562 | return result; 563 | } 564 | } 565 | 566 | -------------------------------------------------------------------------------- /src/components/PositionMarker/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import { Text, View } from 'react-native'; 6 | import { Marker } from 'react-native-maps'; 7 | import connectTheme from '../../themes' 8 | import Styles from './styles'; 9 | import PropTypes from "prop-types"; 10 | import { POSITION_DOT, POSITION_ARROW } from "../../constants/MarkerTypes"; 11 | 12 | 13 | /** 14 | * @class 15 | */ 16 | export default class PositionMarker extends Component { 17 | 18 | /** 19 | * propTypes 20 | * @type {} 21 | */ 22 | static propTypes = { 23 | coordinate: PropTypes.object, 24 | size: PropTypes.number, 25 | fontSize: PropTypes.number, 26 | type: PropTypes.any, 27 | color: PropTypes.string, 28 | angle: PropTypes.number, 29 | backgroundColor: PropTypes.string, 30 | borderColor: PropTypes.string, 31 | borderWidth: PropTypes.number, 32 | } 33 | 34 | /** 35 | * defaultProps 36 | * @type {} 37 | */ 38 | static defaultProps = { 39 | coordinate: undefined, 40 | size: 40, 41 | fontSize: 30, 42 | type: POSITION_DOT, 43 | color: '#252525', 44 | angle: 60, 45 | borderWidth: 0, 46 | borderColor: undefined, 47 | backgroundColor: '#252525' 48 | } 49 | 50 | 51 | /** 52 | * constructor 53 | * @param props 54 | */ 55 | constructor(props) 56 | { 57 | super(props); 58 | } 59 | 60 | /** 61 | * render 62 | * @render 63 | * @returns {*} 64 | */ 65 | render() 66 | { 67 | if(!this.props.coordinate) return null; 68 | 69 | const type = this.props.type; 70 | 71 | this.theme = connectTheme(this.props.theme).Markers[type]; 72 | 73 | const styles = Styles(Object.assign({}, this.props, this.theme)); 74 | 75 | return (type == POSITION_ARROW) ? this.renderArrow(styles) : this.renderDot(styles); 76 | } 77 | 78 | /** 79 | * renderArrow 80 | * @param styles 81 | * @returns {*} 82 | */ 83 | renderArrow(styles) 84 | { 85 | return ( 86 | 90 | 91 | {this.theme.icon} 92 | 93 | 94 | ) 95 | } 96 | 97 | 98 | /** 99 | * renderDot 100 | * @param styles 101 | * @returns {*} 102 | */ 103 | renderDot(styles) { 104 | 105 | return ( 106 | 110 | 111 | {this.theme.icon} 112 | 113 | 114 | ) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/components/PositionMarker/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { NavigationIconsFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | positionMarkerText: { 14 | ...NavigationIconsFont, 15 | fontSize: props.fontSize, 16 | color: props.color, 17 | }, 18 | 19 | positionMarkerArrow: { 20 | backgroundColor: props.backgroundColor, 21 | width: props.size, 22 | height: props.size, 23 | borderRadius: props.size, 24 | justifyContent: 'center', 25 | alignItems: 'center', 26 | transform: [ 27 | { rotateX: props.angle + 'deg'} 28 | ] 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/RouteMarker/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import { Text } from 'react-native'; 6 | import { Marker } from 'react-native-maps'; 7 | import connectTheme from '../../themes' 8 | import Styles from './styles'; 9 | 10 | 11 | /** 12 | * @class 13 | */ 14 | export default class RouterMarker extends Component { 15 | 16 | /** 17 | * constructor 18 | * @param props 19 | */ 20 | constructor(props) { 21 | super(props); 22 | 23 | this.theme = connectTheme(props.theme).Markers[this.props.type]; 24 | } 25 | 26 | 27 | /** 28 | * @render 29 | * @returns {*} 30 | */ 31 | render() { 32 | 33 | const styles = Styles(this.theme); 34 | 35 | return ( 36 | 39 | {this.theme.icon} 40 | 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/RouteMarker/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { NavigationIconsFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | markerText: { 14 | ...NavigationIconsFont, 15 | fontSize: props.fontSize || 30, 16 | color: props.color || '#000000', 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/RoutePolyline/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import { Text } from 'react-native'; 6 | import { Polyline } from 'react-native-maps'; 7 | import connectTheme from '../../themes' 8 | 9 | 10 | /** 11 | * @class 12 | */ 13 | export default class RoutePolyline extends Component { 14 | 15 | /** 16 | * constructor 17 | * @param props 18 | */ 19 | constructor(props) { 20 | super(props); 21 | 22 | this.theme = connectTheme(props.theme).Polylines[this.props.type]; 23 | } 24 | 25 | 26 | /** 27 | * @render 28 | * @returns {*} 29 | */ 30 | render() { 31 | 32 | if(!this.props.coordinates) return null; 33 | 34 | if(!this.theme) { 35 | throw new Error("RoutePolyline does not support type " + this.props.type + "."); 36 | } 37 | 38 | const components = [ 39 | 46 | ]; 47 | 48 | if(this.theme.fillColor) { 49 | 50 | const borderWidth = this.theme.strokeWidth - (this.theme.borderWidth || 3); 51 | 52 | components.push( 53 | 60 | ); 61 | } 62 | 63 | return components; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/TravelModeBox/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { View, TouchableOpacity, Text } from 'react-native'; 7 | import OptionGroupBox from 'react-native-optiongroup'; 8 | import {DEFAULT_MODES, MODE_MAPPING, DRIVING} from '../../constants/TravelModes'; 9 | import {NavigationIconsFont} from '../../constants/NavigationIcons'; 10 | 11 | /** 12 | * @component 13 | */ 14 | export default class TravelModeBox extends Component { 15 | 16 | /** 17 | * propTypes 18 | * @type {} 19 | */ 20 | static propTypes = { 21 | backgroundColor: PropTypes.string, 22 | borderColor: PropTypes.string, 23 | borderWidth: PropTypes.number, 24 | borderRadius: PropTypes.number, 25 | contentPadding: PropTypes.number, 26 | inverseTextColor: PropTypes.string, 27 | modes: PropTypes.array, 28 | selected: PropTypes.any, 29 | defaultValue: PropTypes.any, 30 | style: PropTypes.any, 31 | onChange: PropTypes.func, 32 | theme: PropTypes.string, 33 | invertKeyLabel: PropTypes.bool, 34 | fontFamily: PropTypes.string, 35 | fontSize: PropTypes.number, 36 | useIcons: PropTypes.bool, 37 | } 38 | 39 | /** 40 | * defaultProps 41 | * @type {} 42 | */ 43 | static defaultProps = { 44 | backgroundColor: 'transparent', 45 | borderColor: '#FFFFFF', 46 | borderWidth: 1, 47 | borderRadius: 3, 48 | contentPadding: 10, 49 | inverseTextColor: '#FFFFFF', 50 | defaultValue: DRIVING, 51 | selected: undefined, 52 | style: {}, 53 | onChange: undefined, 54 | theme: undefined, 55 | invertKeyLabel: false, 56 | fontSize: 25, 57 | fontFamily: undefined, 58 | useIcons: true, 59 | modes: DEFAULT_MODES, 60 | } 61 | 62 | /** 63 | * @constructor 64 | * @param props 65 | */ 66 | constructor(props) 67 | { 68 | super(props); 69 | } 70 | 71 | /** 72 | * render 73 | * @returns {XML} 74 | */ 75 | render() 76 | { 77 | const options = []; 78 | 79 | this.props.modes.map(mode => { 80 | if (MODE_MAPPING[mode]) { 81 | options.push(MODE_MAPPING[mode]) 82 | } 83 | }); 84 | 85 | return ( 86 | 93 | ); 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/components/TravelModeLabel/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import React, { Component } from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import { View, TouchableOpacity, Text } from 'react-native'; 7 | import {MODE_MAPPING} from '../../constants/TravelModes'; 8 | import Styles from "./styles"; 9 | 10 | /** 11 | * @component 12 | */ 13 | export default class TravelModeLabel extends Component { 14 | 15 | /** 16 | * propTypes 17 | * @type {} 18 | */ 19 | static propTypes = { 20 | size: PropTypes.number, 21 | opacity: PropTypes.number, 22 | color: PropTypes.string, 23 | fontFamily: PropTypes.string, 24 | fontSize: PropTypes.number, 25 | useIcon: PropTypes.bool, 26 | useLabel: PropTypes.bool, 27 | mode: PropTypes.string 28 | } 29 | 30 | /** 31 | * defaultProps 32 | * @type {} 33 | */ 34 | static defaultProps = { 35 | color: undefined, 36 | opacity: 0.8, 37 | fontSize: 25, 38 | fontFamily: undefined, 39 | useIcon: true, 40 | useLabel: true, 41 | mode: undefined, 42 | } 43 | 44 | /** 45 | * @constructor 46 | * @param props 47 | */ 48 | constructor(props) 49 | { 50 | super(props); 51 | 52 | } 53 | 54 | /** 55 | * render 56 | * @returns {XML} 57 | */ 58 | render() 59 | { 60 | const styles = Styles(this.props); 61 | 62 | const travelMode = MODE_MAPPING[this.props.mode]; 63 | 64 | if(!travelMode) return null; 65 | 66 | return ( 67 | 68 | {!this.useIcon ? null : ( 69 | 70 | {travelMode.icon} 71 | 72 | )} 73 | 74 | {!this.useLabel ? null : ( 75 | 76 | {travelMode.name} 77 | 78 | )} 79 | 80 | ); 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/components/TravelModeLabel/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import { StyleSheet} from 'react-native'; 5 | import { NavigationIconsFont } from '../../constants/NavigationIcons'; 6 | 7 | 8 | /** 9 | * @styles 10 | */ 11 | export default props => StyleSheet.create({ 12 | 13 | /** 14 | * @travelModeLabelContainer 15 | */ 16 | travelModeLabelContainer: { 17 | flexDirection: 'row' 18 | }, 19 | 20 | travelModeLabelIcon: { 21 | ...NavigationIconsFont, 22 | fontSize: props.size, 23 | opacity: props.opacity 24 | }, 25 | 26 | travelModeLabelText: { 27 | fontFamily: props.fontFamily, 28 | fontSize: props.fontSize, 29 | opacity: props.opacity, 30 | flexWrap: 'wrap', 31 | }, 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /src/constants/DirectionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {string} 3 | */ 4 | export const DEFAULT_DIRECTION_TYPE = 'invalid'; 5 | 6 | /** 7 | * @DirectionTypes 8 | */ 9 | export default { 10 | NORTH: 'NORTH', 11 | NORTHEAST: 'NORTHEAST', 12 | EAST: 'EAST', 13 | SOUTHEAST: 'SOUTHEAST', 14 | SOUTH: 'SOUTH', 15 | SOUTHWEST: 'SOUTHWEST', 16 | WEST: 'WEST', 17 | NORTHWEST: 'NORTHWEST', 18 | }; -------------------------------------------------------------------------------- /src/constants/MarkerTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {string} 3 | */ 4 | export const ORIGIN = 'ORIGIN'; 5 | 6 | /** 7 | * @type {string} 8 | */ 9 | export const DESTINATION = 'DESTINATION'; 10 | 11 | export const POSITION_DOT = 'POSITION_DOT'; 12 | 13 | export const POSITION_ARROW = 'POSITION_ARROW'; 14 | 15 | -------------------------------------------------------------------------------- /src/constants/NavigationIcons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import DirectionTypes from './DirectionTypes'; 5 | 6 | /** 7 | * @styles 8 | */ 9 | export const NavigationIconsFont = { 10 | fontFamily: 'Navigation' 11 | }; 12 | 13 | /** 14 | * Arrows 15 | * @type {{}} 16 | */ 17 | export default { 18 | 19 | /** 20 | * @common 21 | */ 22 | watch: '\ue957', 23 | timer3: '\ue958', 24 | timer10: '\ue959', 25 | timerAv: '\ue95a', 26 | timer: '\ue95b', 27 | menu: '\ue95c', 28 | zoomOutMap: '\ue95d', 29 | zoomOut: '\ue95e', 30 | zoomIn: '\ue95f', 31 | linearScale: '\ue960', 32 | room: '\ue961', 33 | place: '\ue961', 34 | zoomOutMap2: '\ue962', 35 | locationOff: '\ue963', 36 | locationCity: '\ue964', 37 | locationDisabled: '\ue965', 38 | locationSearching: '\ue966', 39 | myLocation: '\ue967', 40 | editLocation: '\ue968', 41 | addLocation: '\ue969', 42 | map: '\ue96a', 43 | directionsTransit: '\ue96b', 44 | directionsWalk: '\ue96c', 45 | directionsBike: '\ue96d', 46 | cardTravel: '\ue96e', 47 | home: '\ue96f', 48 | directionsDriving: '\ue970', 49 | navigate: '\ue975', 50 | target: '\ue971', 51 | location2: '\ue972', 52 | location3: '\ue973', 53 | compassDirection: '\ue974', 54 | compassDot: '\ue977', 55 | 56 | /** 57 | * @arrows 58 | * turn-slight-left, turn-sharp-left, uturn-left, turn-left, turn-slight-right, turn-sharp-right, 59 | * uturn-right, turn-right, straight, ramp-left, ramp-right, merge, fork-left, fork-right, ferry, 60 | * ferry-train, roundabout-left, roundabout-right 61 | */ 62 | arriveLeft: '\ue900', 63 | arriveRight: '\ue901', 64 | arriveStraight: '\ue902', 65 | arrive: '\ue903', 66 | close: '\ue904', 67 | continueLeft: '\ue905', 68 | continueRight: '\ue906', 69 | continueSlightLeft: '\ue907', 70 | continueSlightRight: '\ue908', 71 | continueStraight: '\ue909', 72 | continueUturn: '\ue90a', 73 | continue: '\ue90b', 74 | keepLeft: '\ue907', 75 | keepRight: '\ue908', 76 | departLeft: '\ue90c', 77 | departRight: '\ue90d', 78 | departStraight: '\ue90e', 79 | depart: '\ue90f', 80 | endOfRoadLeft: '\ue910', 81 | endOfRoadRight: '\ue911', 82 | flag: '\ue912', 83 | forkLeft: '\ue913', 84 | forkRight: '\ue914', 85 | forkSlightLeft: '\ue915', 86 | forkSlightRight: '\ue916', 87 | forkStraight: '\ue917', 88 | fork: '\ue918', 89 | invalidLeft: '\ue919', 90 | invalidRight: '\ue91a', 91 | invalidSlightLeft: '\ue91b', 92 | invalidSlightRight: '\ue91c', 93 | invalidStraight: '\ue91d', 94 | invalidUturn: '\ue91e', 95 | invalid: '\ue91f', 96 | straight: '\ue91f', 97 | merge: '\ue976', 98 | mergeLeft: '\ue920', 99 | mergeRight: '\ue921', 100 | mergeSlightLeft: '\ue922', 101 | mergeSlightRight: '\ue923', 102 | mergeStraight: '\ue924', 103 | newNameLeft: '\ue925', 104 | newNameRight: '\ue926', 105 | newNameSharpLeft: '\ue927', 106 | newNameSharpRight: '\ue928', 107 | newNameSlightLeft: '\ue929', 108 | newNameSlightRight: '\ue92a', 109 | newNameStraight: '\ue92b', 110 | notificaitonSharpRight: '\ue92c', 111 | notificationLeft: '\ue92d', 112 | notificationRight: '\ue92e', 113 | notificationSharpLeft: '\ue92f', 114 | notificationSlightLeft: '\ue930', 115 | notificationSlightRight: '\ue931', 116 | notificationStraight: '\ue932', 117 | offRampLeft: '\ue933', 118 | offRampRight: '\ue934', 119 | offRampSlightLeft: '\ue935', 120 | offRampSlightRight: '\ue936', 121 | rampLeft: '\ue933', 122 | rampRight: '\ue934', 123 | rampSlightLeft: '\ue935', 124 | rampSlightRight: '\ue936', 125 | onRampLeft: '\ue937', 126 | onRampRight: '\ue938', 127 | onRampSharpLeft: '\ue939', 128 | onRampSharpRight: '\ue93a', 129 | onRampSlightLeft: '\ue93b', 130 | onRampSlightRight: '\ue93c', 131 | onRampStraight: '\ue93d', 132 | rotaryLeft: '\ue93e', 133 | rotaryRight: '\ue93f', 134 | rotarySharpLeft: '\ue940', 135 | rotarySharpRight: '\ue941', 136 | rotarySlightLeft: '\ue942', 137 | rotarySlightRight: '\ue943', 138 | rotaryStraight: '\ue944', 139 | rotary: '\ue945', 140 | roundaboutLeft: '\ue946', 141 | roundaboutRight: '\ue947', 142 | roundaboutSharpLeft: '\ue948', 143 | roundaboutSharpRight: '\ue949', 144 | roundaboutSlightLeft: '\ue94a', 145 | roundaboutSlightRight: '\ue94b', 146 | roundaboutStraight: '\ue94c', 147 | roundabout: '\ue94d', 148 | turnLeft: '\ue94e', 149 | turnRight: '\ue94f', 150 | turnSharpLeft: '\ue950', 151 | turnSharpRight: '\ue951', 152 | turnSlightLeft: '\ue952', 153 | turnSlightRight: '\ue953', 154 | turnStraight: '\ue954', 155 | updown: '\ue955', 156 | }; 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/constants/NavigationModes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @export 3 | */ 4 | export default { 5 | IDLE: 'IDLE', 6 | ROUTE: 'ROUTE', 7 | NAVIGATION: 'NAVIGATION' 8 | }; -------------------------------------------------------------------------------- /src/constants/PolylineTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {string} 3 | */ 4 | export const ROUTE = 'ROUTE'; 5 | 6 | /** 7 | * @type {string} 8 | */ 9 | export const ROUTE_ALTERNATIVE = 'ROUTE_ALTERNATIVE'; 10 | 11 | /** 12 | * @type {string} 13 | */ 14 | export const PENDING = 'PENDING'; 15 | 16 | /** 17 | * @type {string} 18 | */ 19 | export const NEXT = 'NEXT'; 20 | 21 | -------------------------------------------------------------------------------- /src/constants/PropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Coordinate PropType 3 | * @param props 4 | * @param propName 5 | * @param componentName 6 | * @returns {Error} 7 | * @constructor 8 | */ 9 | export const CoordinatePropType = (props, propName, componentName) => { 10 | 11 | const target = props[propName]; 12 | 13 | if(!target || !target.constructor == Object || !target.latitude || !target.longitude) { 14 | return new Error(propName + ' in ' + componentName + ' requires to be a coordinate object ({latitude, longitude}'); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/constants/TrapTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Trap Options 3 | * @type {{SINGLE_FIRE: string, AUTO_EXPIRE: string}} 4 | */ 5 | export const OPTIONS = { 6 | SINGLE_FIRE: 'SINGLE_FIRE', 7 | AUTO_EXPIRE: 'AUTO_EXPIRE', 8 | }; 9 | 10 | /** 11 | * TrapEvents 12 | * @type {{ENTERED: string, ENTERED_ON_COURSE: string, ENTERED_OFF_COURSE: string, INSIDE: string, INSIDE_CENTER: string, LEAVING: string, LEFT_ON_COURSE: string, LEFT_OFF_COURSE: string}} 13 | */ 14 | export const EVENTS = { 15 | ENTERING: 'ENTERING', 16 | ENTERING_ON_COURSE: 'ENTERING_ON_COURSE', 17 | ENTERING_OFF_COURSE: 'ENTERING_OFF_COURSE', 18 | INSIDE: 'INSIDE', 19 | INSIDE_CENTER: 'INSIDE_CENTER', 20 | LEAVING: 'LEAVING', 21 | LEAVING_ON_COURSE: 'LEAVING_ON_COURSE', 22 | LEAVING_OFF_COURSE: 'LEAVING_OFF_COURSE', 23 | }; 24 | 25 | /** 26 | * 27 | * @type {{OUTSIDE: string, ENTERED: string, INSIDE: string, LEFT: string, EXPIRED: string}} 28 | */ 29 | export const STATES = { 30 | OUTSIDE: 'OUTSIDE', 31 | ENTERED: 'ENTERED', 32 | INSIDE: 'INSIDE', 33 | CENTER: 'CENTER', 34 | LEAVING: 'LEAVING', 35 | LEFT: 'LEFT', 36 | EXPIRED: 'EXPIRED', 37 | } 38 | 39 | /** 40 | * TrapTypes 41 | * @type {{POLYGON: string, CIRCLE: string, STEP: string}} 42 | */ 43 | export const TYPES = { 44 | POLYGON: 'POLYGON', 45 | CIRCLE: 'CIRCLE', 46 | STEP: 'STEP', 47 | } 48 | 49 | /** 50 | * @export 51 | */ 52 | export default { 53 | Events: EVENTS, 54 | States: STATES, 55 | Types: TYPES, 56 | Options: OPTIONS 57 | }; -------------------------------------------------------------------------------- /src/constants/TravelModes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @TravelModes 3 | */ 4 | import NavigationIcons from './NavigationIcons'; 5 | 6 | /** 7 | * @type {string} 8 | */ 9 | export const DRIVING = 'DRIVING'; 10 | 11 | /** 12 | * @type {string} 13 | */ 14 | export const WALKING = 'WALKING'; 15 | 16 | /** 17 | * @type {string} 18 | */ 19 | export const TRANSIT = 'TRANSIT'; 20 | 21 | /** 22 | * @type {string} 23 | */ 24 | export const BICYCLING = 'BICYCLING'; 25 | 26 | /** 27 | * Mapping 28 | * @type {*[]} 29 | */ 30 | export const MODE_MAPPING = { 31 | [DRIVING]: { 32 | mode: DRIVING, 33 | name: 'Driving', 34 | icon: NavigationIcons.directionsDriving, 35 | }, 36 | [WALKING]: { 37 | mode: WALKING, 38 | name: 'Walking', 39 | icon: NavigationIcons.directionsWalk, 40 | }, 41 | [TRANSIT]: { 42 | mode: TRANSIT, 43 | name: 'Transit', 44 | icon: NavigationIcons.directionsTransit, 45 | }, 46 | [BICYCLING]: { 47 | mode: BICYCLING, 48 | name: 'Bicycling', 49 | icon: NavigationIcons.directionsBike, 50 | } 51 | }; 52 | 53 | /** 54 | * Default Modes 55 | * @type {*[]} 56 | */ 57 | export const DEFAULT_MODES = [DRIVING, WALKING, TRANSIT, BICYCLING]; 58 | 59 | 60 | /** 61 | * @default export 62 | */ 63 | export default {DRIVING, WALKING, TRANSIT, BICYCLING} 64 | 65 | -------------------------------------------------------------------------------- /src/modules/Directions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import {toQueryParams, toLatLng, toCoordinate} from './Tools'; 5 | import TravelModes from '../constants/TravelModes'; 6 | import * as MarkerTypes from '../constants/MarkerTypes' 7 | import * as PolylineTypes from '../constants/PolylineTypes'; 8 | import DirectionTypes, { DEFAULT_DIRECTION_TYPE} from '../constants/DirectionTypes'; 9 | import * as GeoLib from 'geolib'; 10 | import NavigationIcons from "../constants/NavigationIcons"; 11 | 12 | /** 13 | * @class 14 | */ 15 | export default class Directions { 16 | 17 | /** 18 | * constructor 19 | * @param apiKey 20 | * @param options 21 | */ 22 | constructor(apiKey, options = false) 23 | { 24 | this.apiKey = apiKey; 25 | this.options = options || {}; 26 | } 27 | 28 | /** 29 | * Fetch route 30 | * @param origin 31 | * @param destination 32 | * @param options 33 | * @returns {Promise} 34 | */ 35 | fetch(origin, destination, options = false) 36 | { 37 | options = Object.assign({ 38 | key: this.apiKey, 39 | mode: TravelModes.DRIVING 40 | }, this.options, options); 41 | 42 | const queryParams = { 43 | origin: toLatLng(origin), 44 | destination: toLatLng(destination), 45 | ...options, 46 | }; 47 | 48 | if(queryParams.mode) queryParams.mode = queryParams.mode.toLowerCase(); 49 | 50 | const url = `https://maps.google.com/maps/api/directions/json?${toQueryParams(queryParams)}`; 51 | 52 | return fetch(url) 53 | .then(response => response.json()) 54 | .then(json => { 55 | 56 | if (json.status !== 'OK') { 57 | const errorMessage = json.error_message || 'Unknown error'; 58 | return Promise.reject(errorMessage); 59 | } 60 | 61 | return this.parse(json); 62 | }); 63 | } 64 | 65 | /** 66 | * parse 67 | * @param json 68 | * @returns {*} 69 | */ 70 | parse(json) 71 | { 72 | // parse each route 73 | if(!json.routes.length) return []; 74 | 75 | return json.routes.map(route => { 76 | 77 | if (!route.legs.length) return null; 78 | 79 | const leg = route.legs[0]; // only support primary leg - waypoint support is later 80 | 81 | // create markers 82 | const markers = [ 83 | // origin 84 | { 85 | coordinate: toCoordinate(leg.start_location), 86 | type: MarkerTypes.ORIGIN, 87 | }, 88 | // destination 89 | { 90 | coordinate: toCoordinate(leg.end_location), 91 | type: MarkerTypes.DESTINATION, 92 | } 93 | ]; 94 | 95 | 96 | const steps = leg.steps.map((step, index) => 97 | this.parseStep( 98 | step, 99 | leg.steps[index + 1] ? leg.steps[index + 1] : false 100 | ) 101 | ); 102 | 103 | steps.push({ 104 | final: true, 105 | bearing: steps[steps.length-1].bearing, 106 | compass: steps[steps.length-1].compass, 107 | start: steps[steps.length-1].end, 108 | end: false, 109 | maneuver: { 110 | name: 'flag', 111 | type: 'flag', 112 | }, 113 | polyline: { 114 | coordinates: [], 115 | type: PolylineTypes.ROUTE 116 | }, 117 | instructions: leg.end_address, 118 | }); 119 | 120 | const polylines = steps.map(step => step.polyline); 121 | 122 | const boundingBox = [toCoordinate(route.bounds.northeast), toCoordinate(route.bounds.southwest)]; 123 | 124 | return { 125 | title: route.summary, 126 | markers, 127 | steps, 128 | polylines, 129 | bounds: { 130 | boundingBox, 131 | center: GeoLib.getCenter(boundingBox), 132 | northEast: toCoordinate(route.bounds.northeast), 133 | southWest: toCoordinate(route.bounds.southwest), 134 | }, 135 | initialBearing: steps.length ? steps[0].bearing : 0, 136 | duration: leg.duration, 137 | distance: leg.distance, 138 | origin: { 139 | address: leg.start_address, 140 | latlng: leg.start_location, 141 | coordinate: toCoordinate(leg.start_location), 142 | }, 143 | destination: { 144 | address: leg.end_address, 145 | latlng: leg.end_location, 146 | coordinate: toCoordinate(leg.end_location), 147 | }, 148 | 149 | }; 150 | 151 | }); 152 | } 153 | 154 | /** 155 | * parseStep 156 | * @param step 157 | */ 158 | parseStep(step, nextStep) { 159 | 160 | const bearing = GeoLib.getGreatCircleBearing(toCoordinate(step.start_location), toCoordinate(nextStep ? nextStep.start_location : step.end_location)); 161 | 162 | return { 163 | compass: this.decodeCompass(bearing), 164 | maneuver: this.decodeManeuver(step, bearing), 165 | bearing: bearing, 166 | mode: step.travel_mode, 167 | start: toCoordinate(step.start_location), 168 | end: toCoordinate(step.end_location), 169 | polyline: { 170 | coordinates: this.decodePolylineToCoordinates(step.polyline.points), 171 | type: PolylineTypes.ROUTE, 172 | }, 173 | duration: step.duration, 174 | distance: step.distance, 175 | instructions: step.html_instructions, 176 | } 177 | } 178 | 179 | /** 180 | * decodeManeuver 181 | * @param step 182 | * @returns {{name: *, type: *}} 183 | */ 184 | decodeManeuver(step) 185 | { 186 | const maneuver = step.maneuver ? step.maneuver : DEFAULT_DIRECTION_TYPE; 187 | 188 | const name = (maneuver.split("-").map((d, i) => i == 0 ? d : d[0].toUpperCase() + d.slice(1))).join(""); 189 | 190 | return { 191 | name, 192 | type: maneuver 193 | }; 194 | } 195 | 196 | /** 197 | * decodeCompassDirection 198 | * @param bearing 199 | * @returns {{detail: *, simple: *}} 200 | */ 201 | decodeCompass(bearing) 202 | { 203 | if(bearing < 0) return false; 204 | 205 | const compass = [ 206 | DirectionTypes.NORTH, 207 | DirectionTypes.NORTHEAST, 208 | DirectionTypes.EAST, 209 | DirectionTypes.SOUTHEAST, 210 | DirectionTypes.SOUTH, 211 | DirectionTypes.SOUTHWEST, 212 | DirectionTypes.WEST, 213 | DirectionTypes.NORTHWEST 214 | ]; 215 | 216 | const compassSimple = [ 217 | DirectionTypes.NORTH, 218 | DirectionTypes.EAST, 219 | DirectionTypes.SOUTH, 220 | DirectionTypes.WEST, 221 | ]; 222 | 223 | const calc = a => { 224 | let c = Math.ceil(bearing / (360 / a.length)) - 1; 225 | return a[c < 0 ? 0 : (c > (a.length-1)) ? (a.length-1) : c]; 226 | } 227 | 228 | return { 229 | detail: calc(compass), 230 | simple: calc(compassSimple), 231 | }; 232 | } 233 | 234 | 235 | /** 236 | * decodePolylineToCoordinates 237 | * @param t 238 | * @param e 239 | * @returns {{latitude: *, longitude: *}[]} 240 | */ 241 | decodePolylineToCoordinates(t, e) 242 | { 243 | for (var n, o, u = 0, l = 0, r = 0, d = [], h = 0, i = 0, a = null, c = Math.pow(10, e || 5); u < t.length;) { 244 | a = null, h = 0, i = 0; 245 | do a = t.charCodeAt(u++) - 63, i |= (31 & a) << h, h += 5; while (a >= 32); 246 | n = 1 & i ? ~(i >> 1) : i >> 1, h = i = 0; 247 | do a = t.charCodeAt(u++) - 63, i |= (31 & a) << h, h += 5; while (a >= 32); 248 | o = 1 & i ? ~(i >> 1) : i >> 1, l += n, r += o, d.push([l / c, r / c]); 249 | } 250 | 251 | return d = d.map(function(t) { 252 | return { 253 | latitude: t[0], 254 | longitude: t[1], 255 | }; 256 | }); 257 | } 258 | } -------------------------------------------------------------------------------- /src/modules/Geocoder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import {toQueryParams} from './Tools'; 5 | 6 | /** 7 | * @class 8 | */ 9 | export default class Geocoder { 10 | 11 | /** 12 | * AddressComponentMapping 13 | * @type {{street_number: string, route: string, postal_code: string, country: string, locality: string, administrative_area_level_1: string, administrative_area_level_2: string}} 14 | */ 15 | static AddressComponentMapping = { 16 | 'street_number': 'number', 17 | 'route': 'street', 18 | 'postal_code': 'zip', 19 | 'country': 'country', 20 | 'locality': 'city', 21 | 'administrative_area_level_1': 'state', 22 | 'administrative_area_level_2': 'county', 23 | } 24 | 25 | /** 26 | * constructor 27 | * @param apiKey 28 | * @param options 29 | */ 30 | constructor(apiKey, options = false) 31 | { 32 | this.apiKey = apiKey; 33 | this.options = options || {}; 34 | } 35 | 36 | /** 37 | * getFromLocation 38 | * @param params 39 | * @returns {Promise} 40 | */ 41 | async getFromLocation(...params) 42 | { 43 | let queryParams = params.length === 1 && typeof params[0] === 'string' ? {address: params[0]} : params; 44 | 45 | if (!params) return Promise.reject('Not enough parameters'); 46 | 47 | queryParams.key = this.apiKey; 48 | 49 | if (this.options.language) 50 | queryParams.language = this.options.language; 51 | 52 | // build url 53 | const url = `https://maps.google.com/maps/api/geocode/json?${toQueryParams(queryParams)}`; 54 | 55 | let response, data; 56 | 57 | // fetch 58 | try { 59 | response = await fetch(url); 60 | } catch (error) { 61 | return Promise.reject(error); 62 | } 63 | 64 | // parse 65 | try { 66 | data = await response.json(); 67 | } catch (error) { 68 | return Promise.reject(error); 69 | } 70 | 71 | // check response's data 72 | if (data.status !== 'OK') { 73 | return Promise.reject(data); 74 | } 75 | 76 | return data.results; 77 | } 78 | 79 | /** 80 | * getFromLatLng 81 | * @param lat 82 | * @param lng 83 | * @returns {*} 84 | */ 85 | getFromLatLng(lat, lng) 86 | { 87 | return this.getFromLocation({latlng: `${lat},${lng}`}) 88 | } 89 | 90 | /** 91 | * minimizeResults 92 | * @param results 93 | */ 94 | minimizeResults(results) 95 | { 96 | if(results.constructor != Array) return []; 97 | 98 | return results.map(result => { 99 | 100 | let {lat, lng} = result.geometry.location; 101 | 102 | return { 103 | components: this.minimizeAddressComponents(result.address_components), 104 | address: result.formatted_address, 105 | coordinate: { 106 | latitude: lat, 107 | longitude: lng 108 | } 109 | }; 110 | }); 111 | } 112 | 113 | /** 114 | * minimizeAddressComponents 115 | * @param components 116 | */ 117 | minimizeAddressComponents(components) 118 | { 119 | let results = {}; 120 | 121 | const ids = Object.keys(Geocoder.AddressComponentMapping); 122 | 123 | components.forEach(component => { 124 | 125 | let index = ids.indexOf(component.types[0]); 126 | 127 | if(index != -1) { 128 | 129 | results[Geocoder.AddressComponentMapping[ids[index]]] = { 130 | short: component.short_name, 131 | long: component.long_name 132 | }; 133 | 134 | } 135 | }); 136 | 137 | return results; 138 | } 139 | } -------------------------------------------------------------------------------- /src/modules/Simulator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import * as GeoLib from 'geolib'; 5 | 6 | /** 7 | * @class 8 | */ 9 | export default class Simulator { 10 | 11 | /** 12 | * constructor 13 | * @param apiKey 14 | * @param options 15 | */ 16 | constructor(instance) 17 | { 18 | this.instance = instance; 19 | this.speed = 30; 20 | this.turnSpeed = 700; 21 | } 22 | 23 | /** 24 | * start 25 | * @param route 26 | */ 27 | start(route) { 28 | this.pointIndex = 0; 29 | 30 | const steps = route.steps; 31 | 32 | let points = []; 33 | let result = []; 34 | 35 | steps.map(step => step.polyline.coordinates.map(coordinate => points.push(Object.assign({}, coordinate)))); 36 | 37 | 38 | points.forEach((point, index) => { 39 | 40 | const nextPoint = points[index + 1]; 41 | 42 | if(nextPoint && !nextPoint.final == true) { 43 | 44 | // calculate distance between each point 45 | const distance = Math.round(GeoLib.getDistance(point, nextPoint)); 46 | const bearing = GeoLib.getGreatCircleBearing(point, nextPoint); 47 | 48 | if(bearing !== 0) { 49 | 50 | if (distance > 1) { 51 | 52 | for (var x = 1; x < distance; x++) { 53 | 54 | result.push(Object.assign({}, {bearing}, GeoLib.computeDestinationPoint(point, x, bearing))); 55 | } 56 | 57 | } else { 58 | result.push(Object.assign({}, {bearing}, point)); 59 | } 60 | } 61 | } 62 | }); 63 | 64 | this.pointIndex = 0; 65 | this.points = result; 66 | this.lastBearing = false; 67 | 68 | this.drive(); 69 | 70 | } 71 | 72 | drive() 73 | { 74 | const point = this.points[this.pointIndex]; 75 | 76 | let speed = this.speed; 77 | 78 | if(point && point.bearing) { 79 | 80 | let allowPositionUpdate = true; 81 | 82 | 83 | if(this.lastBearing != point.bearing) { 84 | 85 | // check if it's just a small bump 86 | if(point.bearing > this.lastBearing - 10 && point.bearing < this.lastBearing + 10) { 87 | 88 | this.instance.updateBearing(point.bearing, this.turnSpeed); 89 | 90 | } else { 91 | allowPositionUpdate = false; 92 | speed = this.turnSpeed; 93 | this.instance.updateBearing(point.bearing, this.turnSpeed); 94 | } 95 | 96 | this.lastBearing = point.bearing; 97 | } 98 | 99 | if(allowPositionUpdate) { 100 | 101 | this.instance.setPosition({ 102 | ...point, 103 | heading: point.bearing, 104 | }); 105 | 106 | this.pointIndex++; 107 | } 108 | 109 | setTimeout(() => this.drive(), speed); 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /src/modules/Tools.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import * as geolib from 'geolib'; 5 | 6 | /** 7 | * toQueryParams 8 | * @param object 9 | * @returns {string} 10 | */ 11 | export const toQueryParams = (object) => 12 | { 13 | return Object.keys(object) 14 | .filter(key => !!object[key]) 15 | .map(key => key + "=" + encodeURIComponent(object[key])) 16 | .join("&") 17 | } 18 | 19 | /** 20 | * toLatLng 21 | * @param value 22 | * @returns {string} 23 | */ 24 | export const toLatLng = (value) => 25 | { 26 | if(value.constructor == String) return value; 27 | 28 | return value && value.latitude && value.longitude ? `${value.latitude},${value.longitude}` : value; 29 | } 30 | 31 | /** 32 | * toCoordinate 33 | * @param latlng 34 | * @returns {{latitude: *, longitude: *}} 35 | */ 36 | export const toCoordinate = (latlng) => 37 | { 38 | const {lat, lng} = latlng; 39 | 40 | return {latitude: lat, longitude: lng}; 41 | } 42 | 43 | /** 44 | * toArcPolygon 45 | * @param coordinate 46 | * @param initialBearing 47 | * @param finalBearing 48 | * @param radius 49 | * @returns {any[]} 50 | */ 51 | export const toArcPolygon = (coordinate, initialBearing, finalBearing, radius) => 52 | { 53 | const d2r = Math.PI / 180; // degrees to radians 54 | const r2d = 180 / Math.PI; // radians to degrees 55 | const points = 32; 56 | let result = []; 57 | 58 | // find the radius in lat/lon 59 | //const rlat = (radius / EARTH_RADIUS_METERS) * r2d; 60 | //const rlng = rlat / Math.cos({coordinate.latitude * d2r); 61 | 62 | if (initialBearing > finalBearing) finalBearing += 360; 63 | let deltaBearing = finalBearing - initialBearing; 64 | deltaBearing = deltaBearing/points; 65 | 66 | for (let i=0; (i < points+1); i++) 67 | { 68 | result.push(geolib.computeDestinationPoint(coordinate, radius, initialBearing + i*deltaBearing)); 69 | } 70 | 71 | return result; 72 | }; 73 | 74 | /** 75 | * toNameId 76 | * @param str 77 | * @param prepend 78 | * @param append 79 | * @returns {*} 80 | */ 81 | export const toNameId = (str, prepend = false, append = false) => 82 | { 83 | str = str.toLowerCase().replace(/_/g, ' ').replace(/\b[a-z]/g, letter => letter.toUpperCase()).replace(/\s/g, ''); 84 | 85 | return (prepend ? prepend : '') + str + (append ? append : ''); 86 | } -------------------------------------------------------------------------------- /src/modules/Traps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @imports 3 | */ 4 | import {toQueryParams, toLatLng, toCoordinate, toNameId} from './Tools'; 5 | import * as GeoLib from 'geolib'; 6 | import TrapTypes from '../constants/TrapTypes'; 7 | 8 | /** 9 | * @class 10 | */ 11 | export default class Traps { 12 | 13 | /** 14 | * constructor 15 | * @param apiKey 16 | * @param options 17 | */ 18 | constructor(instance) { 19 | this.instance = instance; 20 | this.traps = {}; 21 | this.counter = 0; 22 | } 23 | 24 | /** 25 | * execute 26 | * @param position 27 | */ 28 | execute(position) { 29 | const {coordinate, heading, altitude} = position; 30 | 31 | this.__matches(coordinate, heading); 32 | } 33 | 34 | /** 35 | * Add 36 | * @param trap 37 | * @param callback 38 | * @returns {*} 39 | */ 40 | add(trap, callback) { 41 | 42 | this.counter++; 43 | const counter = this.counter; 44 | 45 | this.traps[counter] = Object.assign({}, trap, { 46 | index: counter, 47 | state: TrapTypes.States.OUTSIDE, 48 | callback: callback, 49 | }); 50 | 51 | Object.keys(TrapTypes.States).forEach(state => this.traps[counter][toNameId(state, "is")] = () => this.traps[counter].state === state); 52 | 53 | return this.traps[counter]; 54 | } 55 | 56 | /** 57 | * getArray 58 | * @returns {any[]} 59 | */ 60 | getArray() 61 | { 62 | return Object.keys(this.traps).map(id => this.traps[id]); 63 | } 64 | 65 | 66 | /** 67 | * watchRadius 68 | * @param coordinate 69 | * @param radius 70 | * @param options 71 | */ 72 | watchRadius(coordinate, radius, options, callback) { 73 | 74 | return this.add({ 75 | type: TrapTypes.Types.CIRCLE, 76 | coordinate, 77 | radius, 78 | options 79 | }, callback); 80 | } 81 | 82 | /** 83 | * watchStep 84 | * @param step 85 | * @param nextStep 86 | * @param options 87 | * @param callback 88 | * @returns {*} 89 | */ 90 | watchStep(step, nextStep, options, callback) 91 | { 92 | options = Object.assign({}, { 93 | distance: 15, 94 | innerRadiusTolerance: 0.75, 95 | centerRadiusTolerance: 0.5, 96 | courseTolerance: 30, 97 | }, options); 98 | 99 | const distanceToNextPoint = options.distance || step.distance.value; // in meters 100 | 101 | const coordinate = step.start; 102 | 103 | return this.add({ 104 | type: TrapTypes.Types.STEP, 105 | innerRadius: distanceToNextPoint * options.innerRadiusTolerance, 106 | centerRadius: distanceToNextPoint * options.centerRadiusTolerance, 107 | outerRadius: distanceToNextPoint, 108 | courseTolerance: options.courseTolerance, 109 | coordinate, 110 | step, 111 | nextStep 112 | }, callback); 113 | } 114 | 115 | /** 116 | * nextStatus 117 | * @param trap 118 | * @param status 119 | * @returns {*} 120 | */ 121 | nextState(trap, event, state) 122 | { 123 | // set new status 124 | this.traps[trap.index].state = state; 125 | 126 | // resolve with status 127 | if(event.constructor == String) { 128 | trap.callback && trap.callback(trap, event, state); 129 | } 130 | } 131 | 132 | /** 133 | * 134 | * @param coordinate 135 | * @private 136 | */ 137 | __matches(coordinate, heading) 138 | { 139 | const traps = Object.keys(this.traps); 140 | 141 | return traps.map(index => { 142 | 143 | const trap = this.traps[index]; 144 | 145 | if(trap.state != TrapTypes.States.EXPIRED) { 146 | 147 | switch (trap.type) { 148 | 149 | case TrapTypes.Types.CIRCLE: 150 | 151 | if(GeoLib.isPointWithinRadius(coordinate, trap.coordinate, trap.radius)) { 152 | 153 | } 154 | 155 | break; 156 | 157 | case TrapTypes.Types.STEP: 158 | 159 | const insideOuter = GeoLib.isPointWithinRadius(coordinate, trap.coordinate, trap.outerRadius); 160 | 161 | const insideInner = GeoLib.isPointWithinRadius(coordinate, trap.coordinate, trap.innerRadius); 162 | 163 | const stateMap = { 164 | [TrapTypes.States.OUTSIDE]: [TrapTypes.States.ENTERED, () => 165 | { 166 | const isWithinCourse = this.isWithinCourse(trap.step.bearing, heading, trap.courseTolerance); 167 | 168 | return insideOuter ? (isWithinCourse ? TrapTypes.Events.ENTERING_ON_COURSE : TrapTypes.Events.ENTERING_OFF_COURSE) : false; 169 | }], 170 | 171 | [TrapTypes.States.ENTERED]: [TrapTypes.States.INSIDE, () => 172 | { 173 | return insideOuter ? TrapTypes.Events.INSIDE : false; 174 | }], 175 | 176 | [TrapTypes.States.INSIDE] : [TrapTypes.States.CENTER, () => 177 | { 178 | return insideInner ? TrapTypes.Events.INSIDE_CENTER : false; 179 | }], 180 | 181 | [TrapTypes.States.CENTER] : [TrapTypes.States.LEAVING, () => 182 | { 183 | const isWithinCourse = this.isWithinCourse(trap.nextStep ? trap.nextStep.bearing : trap.step.bearing, heading, trap.courseTolerance); 184 | 185 | return insideOuter && !insideInner ? (isWithinCourse ? TrapTypes.Events.LEAVING_ON_COURSE : TrapTypes.Events.LEAVING_OFF_COURSE) : false; 186 | }], 187 | 188 | [TrapTypes.States.LEAVING]: [TrapTypes.States.LEFT, () => 189 | { 190 | return !insideOuter && !insideInner ? TrapTypes.Events.LEAVING : false; 191 | }], 192 | 193 | [TrapTypes.States.LEFT]: [TrapTypes.States.EXPIRED, () => 194 | { 195 | return true; 196 | }], 197 | } 198 | 199 | if(stateMap[trap.state]) { 200 | 201 | const func = stateMap[trap.state]; 202 | const event = func[1](); 203 | 204 | if (event) { 205 | 206 | this.nextState(trap, event, func[0]); 207 | 208 | return true; 209 | } 210 | } 211 | 212 | break; 213 | } 214 | } 215 | }); 216 | } 217 | 218 | 219 | /** 220 | * Quantifier 221 | * @param bearing 222 | * @param heading 223 | * @param quantifier 224 | */ 225 | isWithinCourse(bearing, heading, tolerance = 0) 226 | { 227 | const low = bearing - tolerance; 228 | const high = bearing + tolerance; 229 | 230 | return ((low < 0 && heading > (360 - (-1 * low))) || (heading > low)) && ((high > 360 && heading < (high - 360)) || (heading < high)); 231 | } 232 | 233 | 234 | 235 | } -------------------------------------------------------------------------------- /src/themes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @import 3 | */ 4 | import * as MarkerTypes from '../constants/MarkerTypes'; 5 | import * as PolylineTypes from '../constants/PolylineTypes'; 6 | import NavigationIcons from '../constants/NavigationIcons'; 7 | 8 | /** 9 | * defaultThemeSettings 10 | * @type {{[p: string]: *}} 11 | */ 12 | export const defaultThemeSettings = { 13 | 14 | /** 15 | * @markers 16 | */ 17 | Markers: { 18 | [MarkerTypes.ORIGIN]: { 19 | icon: NavigationIcons.place, 20 | color: '#77dd77', 21 | fontSize: 40, 22 | }, 23 | 24 | [MarkerTypes.DESTINATION]: { 25 | icon: NavigationIcons.place, 26 | color: '#ff4500', 27 | fontSize: 40, 28 | }, 29 | 30 | [MarkerTypes.POSITION_DOT]: { 31 | icon: NavigationIcons.compassDot, 32 | color: '#387bc1', 33 | fontSize: 30, 34 | }, 35 | 36 | [MarkerTypes.POSITION_ARROW]: { 37 | icon: NavigationIcons.navigate, 38 | size: 100, 39 | fontSize: 80, 40 | color: '#ffffff', 41 | backgroundColor: '#387bc1' 42 | }, 43 | }, 44 | 45 | Polylines: { 46 | [PolylineTypes.ROUTE]: { 47 | fillColor: '#00b3fd', 48 | strokeColor: '#387bc1', 49 | strokeWidth: 18, 50 | borderWidth: 4, 51 | }, 52 | [PolylineTypes.ROUTE_ALTERNATIVE]: { 53 | fillColor: '#cccccc', 54 | strokeColor: '#a0a0a0', 55 | strokeWidth: 18, 56 | borderWidth: 4, 57 | }, 58 | } 59 | 60 | }; 61 | 62 | 63 | /** 64 | * Theme Combiner 65 | * @param theme 66 | * @returns {*} 67 | */ 68 | const connectTheme = (theme) => 69 | { 70 | return Object.assign({}, defaultThemeSettings, theme); 71 | }; 72 | 73 | /** 74 | * @exports 75 | */ 76 | export default connectTheme; 77 | 78 | --------------------------------------------------------------------------------