- {isSmallScreen ? (
+ {turnNavigation.showUI ? (
+ <>
+
+
+
+
+ >
+ ) : isSmallScreen ? (
@@ -146,6 +164,7 @@ export default function App() {
map={map}
mapOptions={mapOptions}
error={error}
+ turnNavigation={turnNavigation}
encodedValues={info.encoded_values}
drawAreas={settings.drawAreasEnabled}
/>
@@ -163,11 +182,22 @@ interface LayoutProps {
error: ErrorStoreState
encodedValues: object[]
drawAreas: boolean
+ turnNavigation: TurnNavigationStoreState
}
-function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues, drawAreas }: LayoutProps) {
+function LargeScreenLayout({
+ query,
+ route,
+ map,
+ error,
+ mapOptions,
+ encodedValues,
+ drawAreas,
+ turnNavigation,
+}: LayoutProps) {
const [showSidebar, setShowSidebar] = useState(true)
const [showCustomModelBox, setShowCustomModelBox] = useState(false)
+
return (
<>
{showSidebar ? (
@@ -192,7 +222,12 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues
drawAreas={drawAreas}
/>
)}
-
+
{!error.isDismissed && }
@@ -229,7 +265,16 @@ function LargeScreenLayout({ query, route, map, error, mapOptions, encodedValues
)
}
-function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues, drawAreas }: LayoutProps) {
+function SmallScreenLayout({
+ query,
+ route,
+ map,
+ error,
+ mapOptions,
+ encodedValues,
+ drawAreas,
+ turnNavigation,
+}: LayoutProps) {
return (
<>
@@ -238,6 +283,7 @@ function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues
route={route}
error={error}
encodedValues={encodedValues}
+ turnNavigationSettings={turnNavigation.settings}
drawAreas={drawAreas}
map={map}
/>
@@ -251,7 +297,6 @@ function SmallScreenLayout({ query, route, map, error, mapOptions, encodedValues
-
diff --git a/src/Converters.ts b/src/Converters.ts
index fb435e70..1bf6d5be 100644
--- a/src/Converters.ts
+++ b/src/Converters.ts
@@ -13,13 +13,33 @@ export function milliSecondsToText(ms: number) {
}
let distanceFormat: Intl.NumberFormat = new Intl.NumberFormat('en', { maximumFractionDigits: 1 })
-export function setDistanceFormat(_distanceFormat: Intl.NumberFormat) {
- distanceFormat = _distanceFormat
+let distanceFormat2 = new Intl.NumberFormat('en', { maximumFractionDigits: 2 })
+
+export function initDistanceFormat(lang: string) {
+ distanceFormat = new Intl.NumberFormat(lang, { maximumFractionDigits: 1 })
+ distanceFormat2 = new Intl.NumberFormat(lang, { maximumFractionDigits: 2 })
+}
+
+export function kmToMPHIfMiles(value: number, showDistanceInMiles: boolean, roundTo10 = false) {
+ return showDistanceInMiles
+ ? roundTo10
+ ? Math.round(value / 1.60934 / 10.0) * 10
+ : Math.round(value / 1.60934)
+ : Math.round(value)
+}
+
+export function meterToFt(value: number) {
+ return value / 0.3048
+}
+
+export function meterToMiles(value: number) {
+ return value / 1609.34
}
export function metersToText(meters: number, showDistanceInMiles: boolean, forceSmallUnits: boolean = false) {
if (showDistanceInMiles) {
if (meters < 160.934 || forceSmallUnits) return Math.floor(meters / 0.3048) + ' ft'
+ if (meters < 600) return distanceFormat2.format(meters / 1609.34) + ' mi'
return distanceFormat.format(meters / 1609.34) + ' mi'
} else {
if (meters < 1000 || forceSmallUnits) return Math.floor(meters) + ' m'
diff --git a/src/SpeechSynthesizer.ts b/src/SpeechSynthesizer.ts
new file mode 100644
index 00000000..576d253d
--- /dev/null
+++ b/src/SpeechSynthesizer.ts
@@ -0,0 +1,24 @@
+export interface SpeechSynthesizer {
+ synthesize(text: string): void
+}
+
+export class SpeechSynthesizerImpl implements SpeechSynthesizer {
+ private readonly locale: string
+ private readonly speechSynthesisAPIAvailable: boolean
+
+ constructor(locale: string) {
+ this.locale = locale
+ this.speechSynthesisAPIAvailable = 'speechSynthesis' in window
+ }
+
+ synthesize(text: string) {
+ if (this.speechSynthesisAPIAvailable) {
+ let utterance = new SpeechSynthesisUtterance(text)
+ utterance.lang = this.locale
+ if (speechSynthesis.pending) speechSynthesis.cancel()
+ speechSynthesis.speak(utterance)
+ } else {
+ console.log('no speechSynthesis API available')
+ }
+ }
+}
diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts
index 466b1282..af0c939f 100644
--- a/src/actions/Actions.ts
+++ b/src/actions/Actions.ts
@@ -2,6 +2,7 @@ import { Action } from '@/stores/Dispatcher'
import { QueryPoint } from '@/stores/QueryStore'
import { ApiInfo, Bbox, Path, RoutingArgs, RoutingProfile, RoutingResult } from '@/api/graphhopper'
import { PathDetailsPoint } from '@/stores/PathDetailsStore'
+import { TNSettingsState } from '@/stores/TurnNavigationStore'
import { POI } from '@/stores/POIsStore'
import { Settings } from '@/stores/SettingsStore'
import { Coordinate } from '@/utils'
@@ -14,6 +15,50 @@ export class InfoReceived implements Action {
}
}
+export class TurnNavigationStop implements Action {}
+
+export class TurnNavigationStart implements Action {}
+
+export class LocationUpdateSync implements Action {
+ readonly enableViewSync: boolean
+
+ constructor(enableViewSync: boolean) {
+ this.enableViewSync = enableViewSync
+ }
+}
+
+export class LocationUpdate implements Action {
+ readonly coordinate: Coordinate
+ readonly speed: number // in meter/sec
+ readonly heading: number
+ readonly syncView: boolean
+
+ constructor(coordinate: Coordinate, syncView: boolean, speed: number, heading: number) {
+ this.coordinate = coordinate
+ this.speed = speed
+ this.syncView = syncView
+ this.heading = heading
+ }
+}
+
+export class TurnNavigationSettingsUpdate implements Action {
+ readonly settings: TNSettingsState
+
+ constructor(settings: TNSettingsState) {
+ this.settings = settings
+ }
+}
+
+export class TurnNavigationReroutingFailed implements Action {}
+export class TurnNavigationReroutingTimeResetForTest implements Action {}
+export class TurnNavigationRerouting implements Action {
+ readonly path: Path
+
+ constructor(path: Path) {
+ this.path = path
+ }
+}
+
export class SetPoint implements Action {
readonly point: QueryPoint
readonly zoomResponse: boolean
@@ -157,9 +202,11 @@ export class DismissLastError implements Action {}
export class SelectMapLayer implements Action {
readonly layer: string
+ readonly forNavigation: boolean
- constructor(layer: string) {
+ constructor(layer: string, forNavigation: boolean = false) {
this.layer = layer
+ this.forNavigation = forNavigation
}
}
@@ -249,6 +296,9 @@ export class InstructionClicked implements Action {
}
}
+export class ToggleVectorTilesForNavigation implements Action {}
+export class ToggleFullScreenForNavigation implements Action {}
+
export class UpdateSettings implements Action {
readonly updatedSettings: Partial
diff --git a/src/api/Api.ts b/src/api/Api.ts
index d0d5fe60..72b4b50b 100644
--- a/src/api/Api.ts
+++ b/src/api/Api.ts
@@ -295,6 +295,14 @@ export class ApiImpl implements Api {
request['timeout_ms'] = 10000
}
+ if (args.heading) {
+ // for navigation we use heading => we have to disable CH
+ request['ch.disable'] = true
+ request.headings = [args.heading]
+ request.heading_penalty = 120
+ request['timeout_ms'] = 10000
+ }
+
if (
args.points.length <= 2 &&
args.maxAlternativeRoutes > 1 &&
@@ -338,7 +346,7 @@ export class ApiImpl implements Api {
}
}
- private static decodeResult(result: RawResult, is3D: boolean) {
+ public static decodeResult(result: RawResult, is3D: boolean) {
return result.paths
.map((path: RawPath) => {
return {
diff --git a/src/api/graphhopper.d.ts b/src/api/graphhopper.d.ts
index c024e495..8ae7ef26 100644
--- a/src/api/graphhopper.d.ts
+++ b/src/api/graphhopper.d.ts
@@ -7,6 +7,7 @@ export type Bbox = [number, number, number, number]
export interface RoutingArgs {
readonly points: [number, number][]
+ readonly heading?: number
readonly profile: string
readonly maxAlternativeRoutes: number
readonly customModel: CustomModel | null
@@ -20,6 +21,8 @@ export interface RoutingRequest {
points_encoded_multiplier: number
instructions: boolean
elevation: boolean
+ headings?: number[]
+ heading_penalty?: number
'alternative_route.max_paths'?: number
'alternative_route.max_weight_factor'?: number
'ch.disable'?: boolean
@@ -93,16 +96,19 @@ export interface Instruction {
readonly points: number[][]
readonly sign: number
readonly text: string
+ readonly street_name: string
readonly motorway_junction: string
readonly time: number
}
interface Details {
readonly street_name: [number, number, string][]
+ readonly surface: [number, number, string][]
+ readonly road_environment: [number, number, string][]
+ readonly road_class: [number, number, string][]
readonly toll: [number, number, string][]
readonly max_speed: [number, number, number][]
- readonly road_class: [number, number, string][]
- readonly road_environment: [number, number, string][]
+ readonly average_speed: [number, number, number][]
readonly road_access: [number, number, string][]
readonly access_conditional: [number, number, string][]
readonly foot_conditional: [number, number, string][]
diff --git a/src/custom.d.ts b/src/custom.d.ts
index d3374601..12566a33 100644
--- a/src/custom.d.ts
+++ b/src/custom.d.ts
@@ -4,6 +4,10 @@ declare module '*.png'
declare module 'heightgraph/src/heightgraph'
declare module 'custom-model-editor/src/index'
+interface Window {
+ ghSaveFile: ({ fileName: string, mimeType: string, fileContents: xmlString }) => Promise
+}
+
declare module 'config' {
interface ProfileGroup {
readonly options: { profile: string }[]
@@ -12,6 +16,7 @@ declare module 'config' {
const routingApi: string
const geocodingApi: string
const defaultTiles: string
+ const navigationTiles: string
const keys: {
graphhopper: string
omniscale: string
diff --git a/src/index.html b/src/index.html
index 8e49a29e..06adc05b 100644
--- a/src/index.html
+++ b/src/index.html
@@ -3,18 +3,24 @@
-
+
-
+
- GraphHopper Maps | Route Planner
+ GraphHopper Maps | Route Planner and GPS Navigation