Improve 'slopes' input, add compass indicator

This commit is contained in:
Pieter Vander Vennet 2023-12-16 01:29:42 +01:00
parent b7175384f9
commit 7a3cb9fbdd
17 changed files with 268 additions and 108 deletions

View file

@ -1,112 +1,92 @@
<script lang="ts">
import { UIEventSource } from "../../../Logic/UIEventSource"
import { ArrowUpIcon } from "@babeard/svelte-heroicons/mini"
import { ImmutableStore, UIEventSource } from "../../../Logic/UIEventSource"
import Translations from "../../i18n/Translations"
import Tr from "../../Base/Tr.svelte"
import { Orientation } from "../../../Logic/Web/Orientation"
import type { Feature } from "geojson"
import { GeoOperations } from "../../../Logic/GeoOperations"
import If from "../../Base/If.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
export let value: UIEventSource<string>
export let value: UIEventSource<string> = new UIEventSource<string>(undefined)
export let mode: "degrees" | "percentage" = "percentage"
export let preview: UIEventSource<string> = new UIEventSource<string>(undefined)
export let feature: Feature = undefined
export let state: SpecialVisualizationState = undefined
let previewMode: "degrees" | "percentage" = mode
function oppMode(m: "degrees" | "percentage"): "percentage" | "degrees" {
if (m === "degrees") {
return "percentage"
}
return "degrees"
let featureBearing: number = 45
if (feature?.geometry?.type === "LineString") {
/* Bearing between -180 and + 180, positive is clockwise*/
featureBearing = Math.round(GeoOperations.bearing(
feature.geometry.coordinates[0],
feature.geometry.coordinates.at(-1),
))
}
let previewDegrees: UIEventSource<string> = new UIEventSource<string>(undefined)
let previewPercentage: UIEventSource<string> = new UIEventSource<string>(undefined)
function degreesToPercentage(beta: number): string {
const perc = Math.tan(beta * Math.PI / 180) * 100
const rounded = Math.round(perc / 5) * 5
const rounded = Math.round(perc / 2.5) * 2.5
return rounded + "%"
}
export let safetyMargin: number = 10
if (safetyMargin < 5) {
throw "Safetymargin should be at least 5, it is " + JSON.stringify(safetyMargin)
}
const orientation = Orientation.singleton
orientation.startMeasurements()
const alpha = orientation.alpha
const beta = orientation.beta
let alpha = new UIEventSource<number>(undefined)
let beta = new UIEventSource<number>(undefined)
let gamma = new UIEventSource<number>(45)
let abs = new UIEventSource<number>(undefined)
let gotMeasurement = orientation.gotMeasurement
let gotMeasurement = new UIEventSource(false)
let arrowDirection: number = undefined
function handleOrientation(event) {
gotMeasurement.setData(true)
// IF the phone is lying flat, then:
// alpha is the compass direction (but not absolute)
// beta is tilt if you would lift the phone towards you
// gamma is rotation if you rotate the phone along the long axis
alpha.setData(Math.floor(event.alpha))
beta.setData(Math.floor(event.beta))
gamma.setData(Math.floor(event.gamma))
abs.setData((event.absolute))
if (beta.data < 0) {
arrowDirection = gamma.data + 180
} else {
arrowDirection = -gamma.data
let valuesign = alpha.map(phoneBearing => {
if (featureBearing === undefined) {
return 1
}
}
// are we going _with_ or _against_ the direction of the feature?
console.log("Starting device orientation listener")
try {
window.addEventListener("deviceorientation", e => handleOrientation(e))
} catch (e) {
console.log("Could not init device orientation api due to", e)
}
if (featureBearing < 0) {
featureBearing += 360
}
let relativeAngle = Math.abs(featureBearing - phoneBearing) % 360
if (relativeAngle < 90 || relativeAngle > 270) {
return 1
} else {
return -1
}
})
beta.map(beta => {
if (-safetyMargin < arrowDirection && arrowDirection < safetyMargin) {
if (mode === "degrees") {
value.setData("" + beta + "°")
} else {
value.setData(degreesToPercentage(beta))
}
if (previewMode === "degrees") {
preview.setData("" + beta + "°")
} else {
preview.setData(degreesToPercentage(beta))
}
// As one moves forward on a way, a positive incline gets higher, and a negative incline gets lower.
let valueSign = valuesign.data
if (mode === "degrees") {
value.setData(valueSign * beta + "°")
} else {
value.setData(undefined)
value.setData(degreesToPercentage(valueSign * beta))
}
}, [beta])
previewDegrees.setData(beta + "°")
previewPercentage.setData(degreesToPercentage(beta))
}, [valuesign, beta])
</script>
{#if $gotMeasurement}
<div class="flex flex-col m-2">
<div class="flex w-full">
<div class="shrink-0 relative w-32 h-32 p-0 m-0 overflow-hidden"
style="border-radius: 9999px; background: greenyellow">
<div class="absolute top-0 left-0 w-16 h-16 interactive"
style={`transform: rotate( ${-safetyMargin}deg ); transform-origin: 100% 100%`} />
<div class="absolute top-0 left-0 w-16 h-16 interactive"
style={`transform: rotate( ${90+safetyMargin}deg ); transform-origin: 100% 100%`} />
<div class="absolute top-0 mt-8 left-0 w-32 h-32 interactive" />
<div class="absolute w-30 h-30 top-0 left-0 rounded-full">
<ArrowUpIcon class="" style={`transform: rotate( ${arrowDirection}deg )`} />
<div class="font-bold w-full flex justify-around items-center text-5xl">
<div>
{$previewDegrees}
</div>
<div>
{$previewPercentage}
</div>
</div>
<div class="font-bold w-full flex justify-center items-center">
{#if $value}
<div class="text-5xl" on:click={() => {previewMode = oppMode(previewMode)}}>
{$preview}
</div>
{:else}
<Tr cls="alert" t={Translations.t.validation.slope.inputIncorrect} />
{/if}
</div>
</div>
@ -114,5 +94,16 @@
<div>
<Tr t={Translations.t.validation.slope.inputExplanation} />
</div>
<If condition={state?.featureSwitchIsTesting ?? new ImmutableStore(true)}>
<span class="subtle">
Way: {featureBearing}°, compass: {$alpha}°, diff: {(featureBearing - $alpha)}
{#if $valuesign === 1}
Forward
{:else}
Backward
{/if}
</span>
</If>
</div>
{/if}

View file

@ -9,7 +9,6 @@
import InputHelpers from "./InputHelpers"
import ToSvelte from "../Base/ToSvelte.svelte"
import type { Feature } from "geojson"
import { createEventDispatcher } from "svelte"
import ImageHelper from "./Helpers/ImageHelper.svelte"
import TranslationInput from "./Helpers/TranslationInput.svelte"
import TagInput from "./Helpers/TagInput.svelte"
@ -19,17 +18,16 @@
import ColorInput from "./Helpers/ColorInput.svelte"
import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte"
import SlopeInput from "./Helpers/SlopeInput.svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
export let type: ValidatorType
export let value: UIEventSource<string | object>
export let feature: Feature
export let args: (string | number | boolean)[] = undefined
export let state: SpecialVisualizationState
let properties = { feature, args: args ?? [] }
let dispatch = createEventDispatcher<{
selected
}>()
</script>
{#if type === "translation"}
@ -49,7 +47,7 @@
{:else if type === "opening_hours"}
<OpeningHoursInput {value} />
{:else if type === "slope"}
<SlopeInput {value} />
<SlopeInput {value} {feature} {state} />
{:else if type === "wikidata"}
<ToSvelte construct={() => InputHelpers.constructWikidataHelper(value, properties)} />
{/if}

View file

@ -1,8 +1,13 @@
import NatValidator from "./NatValidator"
import FloatValidator from "./FloatValidator"
export default class SlopeValidator extends NatValidator {
export default class SlopeValidator extends FloatValidator {
constructor() {
super("slope", "Validates that the slope is a valid number")
super(
"slope",
"Validates that the slope is a valid number." +
"The accompanying input element uses the gyroscope and the compass to determine the correct incline. The sign of the incline will be set automatically. The bearing of the way is compared to the bearing of the compass, as such, the device knows if it is measuring in the forward or backward direction."
)
}
isValid(str: string): boolean {
if (str.endsWith("%") || str.endsWith("°")) {