Manage map initialization sequence better + Fix map/project radio buttons

This commit is contained in:
2026-04-24 16:44:11 +02:00
parent 635b3781e3
commit eb0ded0d26
6 changed files with 115 additions and 92 deletions

View File

@@ -72,9 +72,9 @@ export default {
},
watch: {
baseMap(sNewBaseMap, sOldBaseMap) {
if(this.map.isStyleLoaded()) {
if(sOldBaseMap) this.map.setLayoutProperty(sOldBaseMap, 'visibility', 'none');
if(sNewBaseMap) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible');
if(this.map?.isStyleLoaded()) {
if(sOldBaseMap && this.map.getLayer(sOldBaseMap)) this.map.setLayoutProperty(sOldBaseMap, 'visibility', 'none');
if(sNewBaseMap && this.map.getLayer(sNewBaseMap)) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible');
}
},
},
@@ -190,45 +190,25 @@ export default {
const oMarkersPromise = this.spot.get2('markers', {id_project: this.currProject.id});
const oTrackPromise = this.spot.get2('geojson', {id_project: this.currProject.id});
//Get Map Info
const aoMarkers = await oMarkersPromise;
this.baseMap = null;
this.baseMaps = aoMarkers.maps;
this.markers.messages = aoMarkers.messages;
this.markers.medias = aoMarkers.medias;
this.lastUpdate = aoMarkers.last_update;
//Base maps (raster tiles)
let asSources = {};
let asLayers = [];
for(const asBaseMap of this.baseMaps) {
asSources[asBaseMap.codename] = {
type: 'raster',
tiles: [asBaseMap.pattern],
tileSize: asBaseMap.tile_size
};
asLayers.push({
id: asBaseMap.codename,
type: 'raster',
source: asBaseMap.codename,
'layout': {'visibility': 'none'},
minZoom: asBaseMap.min_zoom,
maxZoom: asBaseMap.max_zoom
});
}
//Map
//Build Map
if(this.map) this.map.remove();
this.map = new Map({
container: 'map',
style: {
version: 8,
sources: asSources,
layers: asLayers
sources: {},
layers: []
},
attributionControl: false
});
const oMarkerImagePromise = this.map.loadImage('images/footprint_mapbox.png');
//Parse Map Info
const aoMarkers = await oMarkersPromise;
this.baseMaps = aoMarkers.maps;
this.baseMap = this.baseMaps.find((asBM) => asBM.default_map)?.codename ?? null;
this.markers.messages = aoMarkers.messages;
this.markers.medias = aoMarkers.medias;
this.lastUpdate = aoMarkers.last_update;
//Force wait for load event
await new Promise((resolve) => {
@@ -236,11 +216,39 @@ export default {
else this.map.once('load', resolve);
});
//Default Basemap
this.baseMap = this.baseMaps.filter((asBM) => asBM.default_map)[0].codename;
//Base maps (raster tiles)
for(const asBaseMap of this.baseMaps) {
this.map.addSource(asBaseMap.codename, {
type: 'raster',
tiles: [asBaseMap.pattern],
tileSize: asBaseMap.tile_size
});
this.map.addLayer({
id: asBaseMap.codename,
type: 'raster',
source: asBaseMap.codename,
'layout': {'visibility': asBaseMap.codename == this.baseMap ? 'visible' : 'none'},
minZoom: asBaseMap.min_zoom,
maxZoom: asBaseMap.max_zoom
});
}
//Get track
//Add track
const oTrack = await oTrackPromise;
this.addTrack(oTrack);
//Centering map
await this.positionMap(oTrack);
//Add Markers
this.addMarkers();
//Force wait for idle event
await new Promise((resolve) => {
this.map.once('idle', resolve);
});
},
addTrack(oTrack) {
this.map.addSource('track', {
'type': 'geojson',
'data': oTrack
@@ -248,9 +256,9 @@ export default {
//Color mapping
let asColorMapping = ['match', ['get', 'type']];
for(const sHikeType in this.hikes.colors) {
for(const [sHikeType, sColor] of Object.entries(this.hikes.colors)) {
asColorMapping.push(sHikeType);
asColorMapping.push(this.hikes.colors[sHikeType]);
asColorMapping.push(sColor);
}
asColorMapping.push('black'); //fallback value
@@ -268,9 +276,9 @@ export default {
'line-width': this.hikes.width
}
});
//Markers
this.map.addImage('markerIcon', (await oMarkerImagePromise).data);
},
async addMarkers() {
this.map.addImage('markerIcon', (await this.map.loadImage('images/footprint_mapbox.png')).data);
this.map.addSource('markers', {
type:'geojson',
data: {
@@ -305,7 +313,7 @@ export default {
//TODO Use same way of displaying markers (so that openMarkerPopup works on all markers)
this.markers.medias.forEach(msg => {
const $Marker = document.createElement('div');
const app = createApp(SpotIconStack, {iconMain: 'message', iconSub:'image'});
const app = createApp(SpotIconStack, {iconMain: 'media', iconSub: msg.subtype+'-in'});
app.mount($Marker);
const $Popup = document.createElement('div');
@@ -332,21 +340,22 @@ export default {
.setPopup(popupElement)
.addTo(this.map);
});
//Centering map
},
async positionMap(oTrack) {
let bOpenFeedPanel = !this.mobile;
let oBounds = new LngLatBounds();
if(
if( //Blog Mode: Fit to last message
this.currProject.mode == this.spot.consts.modes.blog &&
this.markers.messages.length > 0 &&
this.$parent.hash.items[2] != 'message'
) {
//Fit to last message
let oLastMsg = this.markers.messages[this.markers.messages.length - 1];
oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude));
}
else {
//Fit to track
else { //Pre/Histo Mode: Fit to track
for(const iFeatureId in oTrack.features) {
oBounds = oTrack.features[iFeatureId].geometry.coordinates.reduce(
(bounds, coord) => {
@@ -356,6 +365,7 @@ export default {
);
}
}
const iFeedPanelPadding = bOpenFeedPanel?(getOuterWidth(this.$refs.feed)/2):0;
await this.map.fitBounds(
oBounds,
@@ -373,11 +383,6 @@ export default {
//Toggle only when map is ready, for the tilt effet
this.toggleFeedPanel(bOpenFeedPanel);
//Force wait for idle event
await new Promise((resolve) => {
this.map.once('idle', resolve);
});
},
convertMsgToFeatures(oMsg) {
return oMsg.map(oMsg => ({
@@ -640,9 +645,9 @@ export default {
<div class="settings-section">
<h1><SpotIcon :icon="'project'" :classes="'fa-fw'" :text="spot.lang('hikes')" /></h1>
<div class="settings-section-body">
<div class="radio" v-for="project in projects">
<input type="radio" :id="project.id" :value="project.codename" v-model="$parent.hash.items[0]" />
<label :for="project.id">
<div class="radio" v-for="project in projects" :key="'project-'+project.id">
<input type="radio" :id="'project-'+project.id" :value="project.codename" v-model="$parent.hash.items[0]" />
<label :for="'project-'+project.id">
<span>{{ project.name }}</span>
<a class="download" :href="project.gpxfilepath" :title="spot.lang('track_download')" @click.stop="()=>{}">
<SpotIcon :icon="'download'" :classes="'push-left'" />
@@ -654,9 +659,9 @@ export default {
<div class="settings-section">
<h1><SpotIcon :icon="'map'" :classes="'fa-fw'" :text="spot.lang('maps')" /></h1>
<div class="settings-section-body">
<div class="radio" v-for="bm in baseMaps">
<input type="radio" :id="bm.id_map" :value="bm.codename" v-model="baseMap" />
<label :for="bm.id_map">{{ this.spot.lang('map_'+bm.codename) }}</label>
<div class="radio" v-for="bm in baseMaps" :key="'map-'+bm.id_map">
<input type="radio" :id="'map-'+bm.id_map" :value="bm.codename" v-model="baseMap" />
<label :for="'map-'+bm.id_map">{{ this.spot.lang('map_'+bm.codename) }}</label>
</div>
</div>
</div>