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}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue