feat: elevation profile with distance axis, rename app, add ABI splits

- Show distance (not time) on X-axis for elevation and speed charts
- Rename app from "Bincio" to "Bincio Autarchive"
- Reinstate arm64-v8a and armeabi-v7a ABI splits for separate APK builds
- Add Haversine distance calculation from GPS coordinates
This commit is contained in:
Davide Scaini
2026-06-03 00:08:45 +02:00
parent 2a7c5fa269
commit 3522568ac3
4 changed files with 77 additions and 11 deletions
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>
+2 -2
View File
@@ -1,7 +1,7 @@
{
"expo": {
"name": "Bincio",
"slug": "bincio",
"name": "Bincio Autarchive",
"slug": "bincio-autarchive",
"version": "0.1.0",
"orientation": "portrait",
"scheme": "bincio",
+54 -9
View File
@@ -21,6 +21,7 @@ type Timeseries = {
power_w?: (number | null)[] | null;
lat?: (number | null)[] | null;
lon?: (number | null)[] | null;
distance_m?: number[] | null;
};
// ── Screen ───────────────────────────────────────────────────────────────────
@@ -361,6 +362,10 @@ function MetricCharts({ timeseries, loading, accent }: { timeseries: Timeseries
const { color, unit, decimals } = TAB_META[tab];
const raw = seriesMap[tab]!;
const distances = timeseries.distance_m
? timeseries.distance_m
: calculateDistanceFromCoords(timeseries.lat, timeseries.lon);
return (
<View style={styles.chartContainer}>
{/* Tab row */}
@@ -378,15 +383,15 @@ function MetricCharts({ timeseries, loading, accent }: { timeseries: Timeseries
))}
</View>
{/* Chart */}
<MetricChart key={tab} times={timeseries.t} values={raw} color={color} unit={unit} decimals={decimals} />
<MetricChart key={tab} distances={distances} values={raw} color={color} unit={unit} decimals={decimals} />
</View>
);
}
function MetricChart({
times, values, color, unit, decimals,
distances, values, color, unit, decimals,
}: {
times: number[];
distances: number[] | null;
values: (number | null)[];
color: string;
unit: string;
@@ -396,25 +401,28 @@ function MetricChart({
const H = 100;
const PAD = 4;
if (!distances) return null;
// Downsample to ≤300 points
const step = Math.max(1, Math.floor(values.length / 300));
const ts = times.filter((_, i) => i % step === 0);
const ds = distances.filter((_, i) => i % step === 0);
const vs = values.filter((_, i) => i % step === 0).map(v => v ?? 0);
const minV = Math.min(...vs);
const maxV = Math.max(...vs);
const range = maxV - minV || 1;
const maxT = ts[ts.length - 1] || 1;
const maxD = ds[ds.length - 1] || 1;
const x = (t: number) => PAD + (t / maxT) * (W - PAD * 2);
const x = (d: number) => PAD + (d / maxD) * (W - PAD * 2);
const y = (v: number) => PAD + (1 - (v - minV) / range) * (H - PAD * 2);
const pts = ts.map((t, i) => `${x(t).toFixed(1)},${y(vs[i]).toFixed(1)}`);
const pts = ds.map((d, i) => `${x(d).toFixed(1)},${y(vs[i]).toFixed(1)}`);
const linePath = `M ${pts.join(' L ')}`;
const areaPath = `M ${x(ts[0])},${H} L ${pts.join(' L ')} L ${x(maxT)},${H} Z`;
const areaPath = `M ${x(ds[0])},${H} L ${pts.join(' L ')} L ${x(maxD)},${H} Z`;
const gradId = `grad-${color.replace('#', '')}`;
const fmt = (v: number) => decimals === 0 ? String(Math.round(v)) : v.toFixed(decimals);
const fmtDist = (d: number) => (d / 1000).toFixed(1);
return (
<>
@@ -429,13 +437,50 @@ function MetricChart({
<Path d={areaPath} fill={`url(#${gradId})`} />
<Path d={linePath} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" />
</Svg>
<Text style={[styles.chartLabel, { color: '#3f3f46', marginBottom: 10 }]}>{fmt(minV)} {unit}</Text>
<Text style={[styles.chartLabel, { color: '#3f3f46', marginBottom: 10 }]}>
{fmt(minV)} {unit} {fmtDist(maxD)} km
</Text>
</>
);
}
// ── Helpers ───────────────────────────────────────────────────────────────────
function calculateDistanceFromCoords(
lats: (number | null)[] | null | undefined,
lons: (number | null)[] | null | undefined,
): number[] {
if (!lats || !lons) return [];
const distances: number[] = [0];
let cumulative = 0;
for (let i = 1; i < lats.length; i++) {
const lat1 = lats[i - 1];
const lon1 = lons[i - 1];
const lat2 = lats[i];
const lon2 = lons[i];
if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) {
distances.push(cumulative);
continue;
}
// Haversine formula
const R = 6371000; // Earth's radius in meters
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
cumulative += R * c;
distances.push(cumulative);
}
return distances;
}
// Returns [west, south, east, north] per LngLatBounds spec
function geoJsonBounds(gj: object): [number, number, number, number] | null {
const coords: [number, number][] = [];