forked from MapComplete/MapComplete
Improve 'slopes' input, add compass indicator
This commit is contained in:
parent
b7175384f9
commit
7a3cb9fbdd
17 changed files with 268 additions and 108 deletions
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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("°")) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue