Fix initial zoom
Deploy Livetrail / deploy (push) Successful in 29s

This commit is contained in:
franzz committed 2026-10-05 15:01:47 +02:00
1 parent 6df463391e
commit 07b6b7b243
5 files changed
+180 -166

No files matched your search

+55 -19
View File
@@ -1,5 +1,5 @@
<script>
import { Map, Marker, LngLatBounds, LngLat, Popup, ScaleControl, NavigationControl, setWorkerUrl } from 'maplibre-gl';
import { Map, Marker, LngLatBounds, LngLat, MercatorCoordinate, Popup, ScaleControl, NavigationControl, setWorkerUrl } from 'maplibre-gl';
import maplibreWorkerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
import 'maplibre-gl/dist/maplibre-gl.css';
@@ -450,19 +450,69 @@ export default {
}
this.map.fitBounds(oBounds, {
padding: BASE_MAP_PADDING,
padding: this.map.getPadding(),
absolutePadding: true,
animate: false,
maxZoom: this.maxZoom,
pitch: this.initialPitch,
bearing: 0
});
this.fixPitchedCameraCenter(aoTrackCoordinates);
this.fixPitchedCamera(aoTrackCoordinates);
}
},
fixPitchedCameraCenter(aoTrackCoordinates) {
fixPitchedCamera(aoTrackCoordinates) {
//fitBounds ignores pitch (still the case in maplibre 6.12): re-center and re-zoom on the projected track.
//Perspective keeps convex shapes convex, so the track screen bounds are always reached on its convex hull
const aoHull = this.getConvexHull(aoTrackCoordinates);
//Re-centering changes perspective distortion: a couple of passes on the hull converge
for(let iPass = 0; iPass < 3; iPass++) {
const oScreenBounds = this.getScreenBounds(aoHull);
const oPadding = this.map.getPadding();
const oContainer = this.map.getContainer();
//Zooming scales the screen around the map center, located at the center of the padded area
const fScale = Math.min(
(oContainer.clientWidth - oPadding.left - oPadding.right) / (oScreenBounds.maxX - oScreenBounds.minX),
(oContainer.clientHeight - oPadding.top - oPadding.bottom) / (oScreenBounds.maxY - oScreenBounds.minY)
);
this.map.jumpTo({
center: this.map.unproject([
(oScreenBounds.minX + oScreenBounds.maxX) / 2,
(oScreenBounds.minY + oScreenBounds.maxY) / 2
]),
zoom: (isFinite(fScale) && fScale > 0)?Math.min(this.map.getZoom() + Math.log2(fScale), this.maxZoom):this.map.getZoom()
});
//Close enough
if(Math.abs(fScale - 1) < 0.01) break;
}
},
getConvexHull(aoCoordinates) {
//Andrew's monotone chain, on mercator coordinates (projected plane)
const aoPoints = aoCoordinates
.map(coord => ({coord, merc: MercatorCoordinate.fromLngLat(coord)}))
.sort((a, b) => (a.merc.x - b.merc.x) || (a.merc.y - b.merc.y));
if(aoPoints.length < 3) return aoCoordinates;
const cross = (o, a, b) => (a.merc.x - o.merc.x) * (b.merc.y - o.merc.y) - (a.merc.y - o.merc.y) * (b.merc.x - o.merc.x);
const buildChain = (aoSortedPoints) => {
const aoChain = [];
for(const oPoint of aoSortedPoints) {
while(aoChain.length >= 2 && cross(aoChain[aoChain.length - 2], aoChain[aoChain.length - 1], oPoint) <= 0) aoChain.pop();
aoChain.push(oPoint);
}
aoChain.pop();
return aoChain;
};
return [...buildChain(aoPoints), ...buildChain([...aoPoints].reverse())].map(oPoint => oPoint.coord);
},
getScreenBounds(aoCoordinates) {
//Project min/max coords (lat, lng) onto map rectangle corner points (x, y)
const oScreenBounds = aoTrackCoordinates.reduce((oBounds, coord) => {
return aoCoordinates.reduce((oBounds, coord) => {
const oPoint = this.map.project(coord);
return {
minX: Math.min(oBounds.minX, oPoint.x),
@@ -476,20 +526,6 @@ export default {
maxX: -Infinity,
maxY: -Infinity
});
//Current Rectangle center
const oTrackCenter = {
x: (oScreenBounds.minX + oScreenBounds.maxX) / 2,
y: (oScreenBounds.minY + oScreenBounds.maxY) / 2
};
//Convert back center point (x, y) to coords and Move map to the track center
this.map.jumpTo({
center: this.map.unproject([
oTrackCenter.x,
oTrackCenter.y
])
});
},
panTo(oLngLat, iZoom, padding, iAnimDuration=500) {
return new Promise((resolve) => {
+4 -4
View File
@@ -66,15 +66,15 @@ export default {
</div>
<div v-if="options.type=='track'">
<div v-if="options.leg" class="section destination">
<appIcon :title="lang.get('stats.from')" icon="start" width="fixed" size="lg" :text="options.leg.from" />
<appIcon :title="lang.get('stats.to')" icon="start" transform="rotate-180" width="fixed" size="lg" :text="options.leg.to" />
<appIcon :title="lang.get('stats.from')" icon="distance" transform="rotate-315" width="fixed" size="lg" :text="options.leg.from" />
<appIcon :title="lang.get('stats.to')" icon="distance" transform="rotate-45" width="fixed" size="lg" :text="options.leg.to" />
</div>
<div v-if="options.leg" class="separator"></div>
<div v-if="options.subtype!='hitchhiking'" class="section track-stats">
<appIcon :title="lang.get('stats.distance')" icon="distance" width="fixed" size="lg" :text="options.distance+'km'" />
<appIcon :title="lang.get('stats.duration')" icon="time" width="fixed" size="lg" :text="options.duration" />
<appIcon :title="lang.get('stats.elevation_gain')" icon="elev-gain" width="fixed" size="lg" :text="options.elev_gain+'m'" />
<appIcon :title="lang.get('stats.elevation_loss')" icon="elev-drop" width="fixed" size="lg" :text="options.elev_drop+'m'" />
<appIcon :title="lang.get('stats.elevation_gain')" icon="distance" transform="rotate-270" width="fixed" size="lg" :text="options.elev_gain+'m'" />
<appIcon :title="lang.get('stats.elevation_loss')" icon="distance" transform="rotate-90" width="fixed" size="lg" :text="options.elev_drop+'m'" />
</div>
</div>
<div v-else-if="options.type=='project'" class="section year">{{ activeTimeInterval }}</div>
-5
View File
@@ -8,11 +8,9 @@ import {
faChevronLeft,
faChevronRight,
faCircleCheck,
faCircleDown,
faCircleExclamation,
faCirclePlay,
faCircleRight,
faCircleUp,
faCircleXmark,
faClock,
faCloudArrowUp,
@@ -100,10 +98,7 @@ const ICONS = {
'hitchhiking': faCarSide,
layers: faLayerGroup,
distance: faCircleRight,
'elev-drop': faCircleDown,
'elev-gain': faCircleUp,
download: faFileArrowDown,
start: faCirclePlay,
/* Settings */
account: faUser,