Marker layer alternative

This commit is contained in:
2026-04-20 00:42:45 +02:00
parent bcc5e9e0cd
commit ef88e600e3
7 changed files with 138 additions and 81 deletions

View File

@@ -288,7 +288,11 @@ class Spot extends Main
$iIndex = 0; $iIndex = 0;
$iMaxIndex = count($asMessages) - 1; $iMaxIndex = count($asMessages) - 1;
foreach($asMedias as $asMedia) { foreach($asMedias as $asMedia) {
if($asMedia['latitude']!='' && $asMedia['longitude']!='') $asGeoMedias[] = $asMedia; if($asMedia['latitude']!='' && $asMedia['longitude']!='') {
$asMedia['lat_dms'] = self::decToDms($asMedia['latitude'], 'lat');
$asMedia['lon_dms'] = self::decToDms($asMedia['longitude'], 'lon');
$asGeoMedias[] = $asMedia;
}
elseif($bHasMsg) { elseif($bHasMsg) {
while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) $iIndex++; while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) $iIndex++;

View File

@@ -10,6 +10,7 @@ import waitforimages from 'jquery.waitforimages';
import lightbox from '../scripts/lightbox.js'; import lightbox from '../scripts/lightbox.js';
import SpotIcon from './spotIcon.vue'; import SpotIcon from './spotIcon.vue';
import SpotIconStack from './spotIconStack.vue';
import SpotButton from './spotButton.vue'; import SpotButton from './spotButton.vue';
import ProjectPost from './projectPost.vue'; import ProjectPost from './projectPost.vue';
import ProjectPopup from './projectPopup.vue'; import ProjectPopup from './projectPopup.vue';
@@ -19,7 +20,6 @@ export default {
SpotIcon, SpotIcon,
SpotButton, SpotButton,
ProjectPost, ProjectPost,
ProjectPopup,
Simplebar Simplebar
}, },
data() { data() {
@@ -30,6 +30,7 @@ export default {
lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''}, lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
feedPanelOpen: false, feedPanelOpen: false,
settingsPanelOpen: false, settingsPanelOpen: false,
markers: {messages: null, medias: null},
markerSize: {width: 32, height: 32}, markerSize: {width: 32, height: 32},
currProject: {}, currProject: {},
modeHisto: false, modeHisto: false,
@@ -38,7 +39,6 @@ export default {
nlLoading: false, nlLoading: false,
baseMaps: {}, baseMaps: {},
baseMap: null, baseMap: null,
messages: null,
map: null, map: null,
hikes: { hikes: {
colors:{'main':'#00ff78', 'off-track':'#0000ff', 'hitchhiking':'#FF7814'}, colors:{'main':'#00ff78', 'off-track':'#0000ff', 'hitchhiking':'#FF7814'},
@@ -190,7 +190,8 @@ export default {
const aoMarkers = await this.spot.get2('markers', {id_project: this.currProject.id}); const aoMarkers = await this.spot.get2('markers', {id_project: this.currProject.id});
this.baseMap = null; this.baseMap = null;
this.baseMaps = aoMarkers.maps; this.baseMaps = aoMarkers.maps;
this.messages = aoMarkers.messages; this.markers.messages = aoMarkers.messages;
this.markers.medias = aoMarkers.medias;
this.lastUpdate = aoMarkers.last_update; this.lastUpdate = aoMarkers.last_update;
//console.log(this.baseMaps); //console.log(this.baseMaps);
@@ -271,7 +272,7 @@ export default {
type:'geojson', type:'geojson',
data: { data: {
type: 'FeatureCollection', type: 'FeatureCollection',
features: this.convertMsgToFeatures(this.messages) features: this.convertMsgToFeatures(this.markers.messages)
} }
}); });
this.map.addLayer({ this.map.addLayer({
@@ -284,16 +285,60 @@ export default {
this.openMarkerPopup(e.features[0]); this.openMarkerPopup(e.features[0]);
}); });
/*
this.markers.messages.forEach(msg => {
const el = document.createElement('div');
const app = createApp(SpotIconStack, {iconMain: 'message', iconSub:'message-in', iconSubClasses:'fa-rotate-270'});
app.mount(el);
new Marker({element: el, anchor: 'bottom'})
.setLngLat([msg.longitude, msg.latitude])
.addTo(this.map);
});
*/
//Medias
this.markers.medias.forEach(msg => {
const $Marker = document.createElement('div');
const app = createApp(SpotIconStack, {iconMain: 'message', iconSub:'image'});
app.mount($Marker);
const $Popup = document.createElement('div');
const popupElement = new Popup({
anchor: 'bottom',
offset: [0, this.markerSize.height * -1],
closeButton: false
})
.setDOMContent($Popup)
.setLngLat([msg.longitude, msg.latitude])
.setMaxWidth(300)
.addTo(this.map)
;
console.log(msg);
const popupContent = createApp(ProjectPopup, {
options: msg,
medias: [msg],
project: this.currProject
});
popupContent.provide('spot', this.spot).mount($Popup);
new Marker({element: $Marker, anchor: 'bottom'})
.setLngLat([msg.longitude, msg.latitude])
.setPopup(popupElement)
.addTo(this.map);
});
//Centering map //Centering map
let bOpenFeedPanel = !this.mobile; let bOpenFeedPanel = !this.mobile;
let oBounds = new LngLatBounds(); let oBounds = new LngLatBounds();
if( if(
this.currProject.mode == this.spot.consts.modes.blog && this.currProject.mode == this.spot.consts.modes.blog &&
this.messages.length > 0 && this.markers.messages.length > 0 &&
this.$parent.hash.items[2] != 'message' this.$parent.hash.items[2] != 'message'
) { ) {
//Fit to last message //Fit to last message
let oLastMsg = this.messages[this.messages.length - 1]; let oLastMsg = this.markers.messages[this.markers.messages.length - 1];
oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude)); oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude));
} }
else { else {
@@ -343,6 +388,50 @@ export default {
} }
})); }));
}, },
openMarkerPopup(oFeature) {
this.closeMarkerPopup();
//Convert ID Message to feature
if(typeof oFeature == 'number') {
const oMatchingFeatures = this.map.querySourceFeatures('markers', {
filter: ['==', ['get', 'id_message'], oFeature]
});
if(!oMatchingFeatures.length) {
console.warn('Marker not found: ', oFeature);
return;
}
else oFeature = oMatchingFeatures[0];
}
const $Popup = document.createElement('div');
this.popup.element = new Popup({
anchor: 'bottom',
offset: [0, this.markerSize.height * -1],
closeButton: false
})
.setDOMContent($Popup)
.setLngLat(oFeature.geometry.coordinates)
.setMaxWidth(300)
.addTo(this.map);
this.popup.content = createApp(ProjectPopup, {
options: oFeature.properties,
medias: JSON.parse(oFeature.properties.medias || '[]'),
project: this.currProject
});
this.popup.content.provide('spot', this.spot).mount($Popup);
},
closeMarkerPopup() {
if(this.popup.content) {
this.popup.content.unmount();
this.popup.content = null;
}
if(this.popup.element) {
this.popup.element.remove();
this.popup.element = null;
}
},
async getNextFeed() { async getNextFeed() {
if(!this.feed.outOfData && !this.feed.loading) { if(!this.feed.outOfData && !this.feed.loading) {
//Get next chunk //Get next chunk
@@ -396,69 +485,14 @@ export default {
}); });
} }
//TODO medias
//Message Last Update //Message Last Update
this.lastUpdate = aoData.last_update; this.lastUpdate = aoData.last_update;
//Reschedule //Reschedule
this.setFeedUpdateTimer(this.refreshRate); this.setFeedUpdateTimer(this.refreshRate);
}, },
openMarkerPopup(oFeature) {
this.closeMarkerPopup();
//Convert ID Message to feature
if(typeof oFeature == 'number') {
const oMatchingFeatures = this.map.querySourceFeatures('markers', {
filter: ['==', ['get', 'id_message'], oFeature]
});
if(!oMatchingFeatures.length) {
console.warn('Marker not found: ', oFeature);
return;
}
else oFeature = oMatchingFeatures[0];
}
const $Popup = document.createElement('div');
this.popup.element = new Popup({
anchor: 'bottom',
offset: [0, this.markerSize.height * -1],
closeButton: false
})
.setDOMContent($Popup)
.setLngLat(oFeature.geometry.coordinates)
.setMaxWidth(300)
.addTo(this.map);
const rProp = ref(oFeature.properties);
const vPopup = defineComponent({
extends: ProjectPopup,
setup: () => {
provide('spot', this.spot);
return {
options: rProp.value,
medias: JSON.parse(rProp.value.medias || '""'),
spot: this.spot,
project: this.currProject
};
}
});
nextTick(() => {
this.popup.content = createApp(vPopup);
this.popup.content.mount($Popup);
});
},
closeMarkerPopup() {
if(this.popup.content) {
this.popup.content.unmount();
this.popup.content = null;
}
if(this.popup.element) {
this.popup.element.remove();
this.popup.element = null;
}
},
panToBetweenPanels(oLngLat, iZoom, fCallback) { panToBetweenPanels(oLngLat, iZoom, fCallback) {
const iXOffset = (this.settingsPanelOpen?getOuterWidth(this.$refs.settings):0) - (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0); const iXOffset = (this.settingsPanelOpen?getOuterWidth(this.$refs.settings):0) - (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0);

View File

@@ -57,7 +57,7 @@ export default {
<span v-if="options.comment" ref="comment" class="lb-caption-line comment desktop"> <span v-if="options.comment" ref="comment" class="lb-caption-line comment desktop">
<spotIcon :icon="'post'" :classes="'fa-lg fa-fw push'" :text-classes="'comment-text'" :text="options.comment" /> <spotIcon :icon="'post'" :classes="'fa-lg fa-fw push'" :text-classes="'comment-text'" :text="options.comment" />
</span> </span>
<span ref="postedon" class="lb-caption-line"> <span ref="postedon" class="lb-caption-line">
<projectRelTime :icon="'upload'" :localTime="options.posted_on_formatted_time_local" :siteTime="options.posted_on_formatted_time" :offset="options.posted_on_day_offset" /> <projectRelTime :icon="'upload'" :localTime="options.posted_on_formatted_time_local" :siteTime="options.posted_on_formatted_time" :offset="options.posted_on_day_offset" />
</span> </span>
<span ref="takenon" class="lb-caption-line"> <span ref="takenon" class="lb-caption-line">

View File

@@ -12,17 +12,12 @@ export default {
projectMediaLink, projectMediaLink,
projectRelTime projectRelTime
}, },
//props: { props: {
// options: Object, options: Object,
//}, medias: Array,
data() { project: Object
return {
}
}, },
//inject: ['options', 'medias', 'spot', 'project'], inject: ['spot']
mounted() {
}
} }
</script> </script>

View File

@@ -1,5 +1,6 @@
<script> <script>
import spotIcon from './spotIcon.vue'; import spotIcon from './spotIcon.vue';
import spotIconStack from './spotIconStack.vue';
import spotButton from './spotButton.vue'; import spotButton from './spotButton.vue';
import projectMediaLink from './projectMediaLink.vue'; import projectMediaLink from './projectMediaLink.vue';
import projectMapLink from './projectMapLink.vue'; import projectMapLink from './projectMapLink.vue';
@@ -11,6 +12,7 @@
export default { export default {
components: { components: {
spotIcon, spotIcon,
spotIconStack,
spotButton, spotButton,
projectMediaLink, projectMediaLink,
projectMapLink, projectMapLink,
@@ -159,10 +161,7 @@
<span class="temperature">{{ options.weather_temp+'°C' }}</span> <span class="temperature">{{ options.weather_temp+'°C' }}</span>
</span> </span>
<img class="staticmap clickable" :title="spot.lang('click_zoom')" :src="options.static_img_url" /> <img class="staticmap clickable" :title="spot.lang('click_zoom')" :src="options.static_img_url" />
<span class="drill-icon fa-stack clickable"> <spotIconStack :iconMain="'message'" :iconSub="'message-in'" :iconSubClasses="'fa-rotate-270'" />
<spotIcon :icon="'message'" :classes="'fa-stack-2x clickable'" />
<spotIcon :icon="'message-in'" :classes="'fa-stack-1x fa-rotate-270'" />
</span>
</a> </a>
</div> </div>
<div v-else-if="options.type == 'media'" class="body-box"> <div v-else-if="options.type == 'media'" class="body-box">

View File

@@ -10,12 +10,15 @@ export default {
}, },
computed: { computed: {
classNames() { classNames() {
return 'fa fa-'+this.icon+((this.margin || this.text && this.text!='')?' push':'')+(this.classes?' '+this.classes:'') return 'fa fa-'+this.icon+((this.margin || this.hasText)?' push':'')+(this.classes?' '+this.classes:'')
},
hasText() {
return this.text && this.text != '';
} }
} }
} }
</script> </script>
<template> <template>
<span :title="title"><i :class="classNames"></i><span :class="textClasses">{{ text }}</span></span> <span :title="title"><i :class="classNames"></i><span v-if="hasText" :class="textClasses">{{ text }}</span></span>
</template> </template>

View File

@@ -0,0 +1,22 @@
<script>
import spotIcon from './spotIcon.vue';
export default {
components: {
spotIcon
},
props: {
iconMain: String,
iconMainClasses: String,
iconSub: String,
iconSubClasses: String,
}
}
</script>
<template>
<span class="drill-icon fa-stack clickable">
<spotIcon :icon="iconMain" :classes="'fa-stack-2x clickable'+(iconMainClasses?' '+iconMainClasses:'')" />
<spotIcon :icon="iconSub" :classes="'fa-stack-1x'+(iconSubClasses?' '+iconSubClasses:'')" />
</span>
</template>