766 lines
30 KiB
HTML
766 lines
30 KiB
HTML
{{/*
|
|
Partial: search-results
|
|
Affiche les résultats de recherche en 2 colonnes :
|
|
- Gauche : accordéons par catégorie
|
|
- Droite : carte MapLibre
|
|
*/}}
|
|
|
|
{{ $iconArrow := resources.Get "images/picto/keyboard_arrow_up_24dp_1F1F1F_FILL1_wght400_GRAD0_opsz24.svg" }}
|
|
{{ $iconArrowRight := resources.Get "images/picto/arrow_right_alt_24dp_1F1F1F_FILL1_wght400_GRAD0_opsz24.svg" }}
|
|
{{ $iconWalk := resources.Get "images/picto/directions_walk_24dp_1F1F1F_FILL1_wght400_GRAD0_opsz24.svg" }}
|
|
{{ $iconBus := resources.Get "images/picto/directions_bus_24dp_1F1F1F_FILL1_wght400_GRAD0_opsz24.svg" }}
|
|
|
|
<div class="search-results-container" x-data="selectionManager()">
|
|
<!-- Colonne gauche : Accordéons -->
|
|
<div class="search-results-accordions">
|
|
<!-- Transports en commun -->
|
|
<div class="result-accordion" x-data="{ open: false }">
|
|
<button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }">
|
|
<span class="accordion-title">Transports en commun</span>
|
|
<span class="accordion-badge" x-text="results.public_transit?.number || 0"></span>
|
|
{{ if $iconArrow }}
|
|
<img src="{{ $iconArrow.RelPermalink }}" alt="" class="accordion-arrow" :class="{ 'open': open }" />
|
|
{{ end }}
|
|
</button>
|
|
<div class="accordion-content" x-show="open" x-collapse x-cloak>
|
|
<div class="accordion-inner">
|
|
<template x-if="results.public_transit?.number > 0">
|
|
<div class="transit-journeys">
|
|
<template x-for="(journey, index) in results.public_transit.results" :key="index">
|
|
<div class="transit-journey" @click="selectItem('transit', index, journey)" :class="{ 'active': isSelected('transit', index) }">
|
|
<!-- En-tête du trajet -->
|
|
<div class="journey-header">
|
|
<div class="journey-date" x-text="formatJourneyDate(journey.startTime)"></div>
|
|
<div class="journey-times">
|
|
<span class="journey-time" x-text="formatTime(journey.startTime)"></span>
|
|
<img src="{{ $iconArrowRight.RelPermalink }}" alt="→" class="journey-arrow" />
|
|
<span class="journey-time" x-text="formatTime(journey.endTime)"></span>
|
|
<span class="journey-duration" x-text="formatDuration(journey.duration)"></span>
|
|
</div>
|
|
</div>
|
|
<!-- Séparateur -->
|
|
<div class="journey-separator"></div>
|
|
<!-- Détail des legs -->
|
|
<div class="journey-legs">
|
|
<template x-for="(leg, legIndex) in journey.legs" :key="legIndex">
|
|
<div class="journey-leg">
|
|
<div class="leg-icon">
|
|
<template x-if="leg.mode === 'WALK'">
|
|
<img src="{{ $iconWalk.RelPermalink }}" alt="Marche" />
|
|
</template>
|
|
<template x-if="leg.mode === 'BUS' || leg.mode === 'COACH'">
|
|
<img src="{{ $iconBus.RelPermalink }}" alt="Bus" />
|
|
</template>
|
|
<template x-if="leg.mode !== 'WALK' && leg.mode !== 'BUS' && leg.mode !== 'COACH'">
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="20" height="20">
|
|
<path d="M4 15.5C4 17.43 5.57 19 7.5 19L6 20.5v.5h12v-.5L16.5 19c1.93 0 3.5-1.57 3.5-3.5V5c0-3.5-3.58-4-8-4s-8 .5-8 4v10.5zm8-12.5c3.52 0 5.78.28 6 1h-12c.22-.72 2.48-1 6-1zM6 7h12v4H6V7zm6 10.5a1.5 1.5 0 110-3 1.5 1.5 0 010 3z"/>
|
|
</svg>
|
|
</template>
|
|
</div>
|
|
<div class="leg-details">
|
|
<template x-if="leg.mode === 'WALK'">
|
|
<div>
|
|
<p class="leg-line">Marche à pied</p>
|
|
<p class="leg-text">
|
|
<span x-text="formatDistance(leg.distance)"></span>
|
|
<span class="leg-duration" x-text="'(' + formatDuration(leg.duration) + ')'"></span>
|
|
</p>
|
|
</div>
|
|
</template>
|
|
<template x-if="leg.mode !== 'WALK'">
|
|
<div>
|
|
<p class="leg-line" x-text="'Ligne ' + (leg.routeShortName || leg.route?.shortName || '')"></p>
|
|
<p class="leg-operator" x-text="leg.agencyName"></p>
|
|
<p class="leg-direction" x-show="leg.headsign">
|
|
Direction <span x-text="leg.headsign"></span>
|
|
</p>
|
|
<div class="leg-timeline">
|
|
<div class="leg-stop leg-stop-departure">
|
|
<span class="leg-stop-dot"></span>
|
|
<span class="leg-stop-time" x-text="formatTime(leg.startTime)"></span>
|
|
<span class="leg-stop-name" x-text="leg.from.name"></span>
|
|
</div>
|
|
<img src="{{ $iconArrowRight.RelPermalink }}" alt="↓" class="leg-timeline-arrow" />
|
|
<div class="leg-stop leg-stop-arrival">
|
|
<span class="leg-stop-dot"></span>
|
|
<span class="leg-stop-time" x-text="formatTime(leg.endTime)"></span>
|
|
<span class="leg-stop-name" x-text="leg.to.name"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
<template x-if="!results.public_transit?.number">
|
|
<p class="no-result-text">Aucun transport en commun disponible pour ce trajet.</p>
|
|
</template>
|
|
{{ with .Params.contactCTA.transit }}
|
|
<div class="accordion-cta">
|
|
<p>{{ . }}</p>
|
|
<a href="tel:{{ site.Params.phone | replaceRE " " "" }}" class="accordion-cta-phone">{{ site.Params.phone }}</a>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Covoiturage -->
|
|
<div class="result-accordion" x-data="{ open: false }">
|
|
<button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }">
|
|
<span class="accordion-title">Covoiturage</span>
|
|
<span class="accordion-badge" x-text="results.carpools?.number || 0"></span>
|
|
{{ if $iconArrow }}
|
|
<img src="{{ $iconArrow.RelPermalink }}" alt="" class="accordion-arrow" :class="{ 'open': open }" />
|
|
{{ end }}
|
|
</button>
|
|
<div class="accordion-content" x-show="open" x-collapse x-cloak>
|
|
<div class="accordion-inner">
|
|
<template x-if="results.carpools?.number > 0">
|
|
<div class="carpool-list">
|
|
<template x-for="(carpool, index) in results.carpools.results" :key="index">
|
|
<div class="carpool-item" @click="selectItem('carpool', index, carpool)" :class="{ 'active': isSelected('carpool', index) }">
|
|
<div class="carpool-preview">
|
|
<div class="carpool-header">
|
|
<span class="carpool-driver">Trajet avec <span x-text="carpool.ocss?.driver?.alias || 'un conducteur'"></span></span>
|
|
<span class="carpool-price" x-show="carpool.ocss?.price?.amount" x-text="formatCarpoolPrice(carpool.ocss?.price?.amount)"></span>
|
|
</div>
|
|
<div class="carpool-date" x-show="carpool.ocss?.passengerPickupDate" x-text="'Départ ' + formatCarpoolDate(carpool.ocss?.passengerPickupDate)"></div>
|
|
</div>
|
|
<template x-if="carpool.ocss?.webUrl">
|
|
<div class="carpool-link-container">
|
|
<a :href="carpool.ocss.webUrl" target="_blank" rel="noopener" class="carpool-operator-link">
|
|
Voir les détails sur <span x-text="carpool.ocss?.operator || 'le site'"></span>
|
|
</a>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
<template x-if="!results.carpools?.number">
|
|
<p class="no-result-text">Aucun covoiturage disponible pour ce trajet.</p>
|
|
</template>
|
|
{{ with .Params.contactCTA.carpool }}
|
|
<div class="accordion-cta">
|
|
<p>{{ . }}</p>
|
|
<a href="tel:{{ site.Params.phone | replaceRE " " "" }}" class="accordion-cta-phone">{{ site.Params.phone }}</a>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transport solidaire -->
|
|
<div class="result-accordion" x-data="{ open: false, get totalDrivers() { return (results.solidarity_drivers?.number || 0) + (results.organized_carpools?.number || 0); } }">
|
|
<button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }">
|
|
<span class="accordion-title">Transport solidaire</span>
|
|
<span class="accordion-badge" x-text="totalDrivers"></span>
|
|
{{ if $iconArrow }}
|
|
<img src="{{ $iconArrow.RelPermalink }}" alt="" class="accordion-arrow" :class="{ 'open': open }" />
|
|
{{ end }}
|
|
</button>
|
|
<div class="accordion-content" x-show="open" x-collapse x-cloak>
|
|
<div class="accordion-inner">
|
|
<template x-if="totalDrivers > 0">
|
|
<div class="solidarity-result">
|
|
<p>Nous avons trouvé</p>
|
|
<p class="solidarity-count" x-text="totalDrivers + ' conducteur' + (totalDrivers > 1 ? 's' : '')"></p>
|
|
<p>disponible<span x-text="totalDrivers > 1 ? 's' : ''"></span> pour faire le trajet que vous recherchez</p>
|
|
</div>
|
|
</template>
|
|
<template x-if="totalDrivers === 0">
|
|
<p class="no-result-text">Aucun transport solidaire disponible pour ce trajet.</p>
|
|
</template>
|
|
{{ with .Params.contactCTA.solidarity }}
|
|
<div class="accordion-cta">
|
|
<p>{{ . }}</p>
|
|
<a href="tel:{{ site.Params.phone | replaceRE " " "" }}" class="accordion-cta-phone">{{ site.Params.phone }}</a>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Location de véhicules -->
|
|
<div class="result-accordion" x-data="{ open: false }">
|
|
<button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }">
|
|
<span class="accordion-title">Location de véhicules</span>
|
|
<span class="accordion-badge" x-text="results.vehicles?.number || 0"></span>
|
|
{{ if $iconArrow }}
|
|
<img src="{{ $iconArrow.RelPermalink }}" alt="" class="accordion-arrow" :class="{ 'open': open }" />
|
|
{{ end }}
|
|
</button>
|
|
<div class="accordion-content" x-show="open" x-collapse x-cloak>
|
|
<div class="accordion-inner">
|
|
<template x-if="results.vehicles?.number > 0">
|
|
<div class="solidarity-result">
|
|
<p>Nous avons trouvé</p>
|
|
<p class="solidarity-count" x-text="results.vehicles.number + ' véhicule' + (results.vehicles.number > 1 ? 's' : '')"></p>
|
|
<p>disponible<span x-text="results.vehicles.number > 1 ? 's' : ''"></span></p>
|
|
</div>
|
|
</template>
|
|
<template x-if="!results.vehicles?.number">
|
|
<p class="no-result-text">Aucun véhicule disponible pour ce trajet.</p>
|
|
</template>
|
|
{{ with .Params.contactCTA.vehicles }}
|
|
<div class="accordion-cta">
|
|
<p>{{ . }}</p>
|
|
<a href="tel:{{ site.Params.phone | replaceRE " " "" }}" class="accordion-cta-phone">{{ site.Params.phone }}</a>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Solutions complémentaires -->
|
|
<div class="result-accordion" x-data="{ open: false }">
|
|
<button type="button" class="accordion-header" @click="open = !open" :class="{ 'active': open }">
|
|
<span class="accordion-title">Solutions complémentaires</span>
|
|
<span class="accordion-badge" x-text="results.local_solutions?.number || 0"></span>
|
|
{{ if $iconArrow }}
|
|
<img src="{{ $iconArrow.RelPermalink }}" alt="" class="accordion-arrow" :class="{ 'open': open }" />
|
|
{{ end }}
|
|
</button>
|
|
<div class="accordion-content" x-show="open" x-collapse x-cloak>
|
|
<div class="accordion-inner">
|
|
<template x-if="results.local_solutions?.number > 0">
|
|
<div class="local-solutions-list">
|
|
<template x-for="(solution, index) in results.local_solutions.results" :key="index">
|
|
<div class="local-solution-item" @click="selectItem('solution', index, solution)" :class="{ 'active': isSelected('solution', index) }">
|
|
<h4 class="local-solution-title" x-text="solution.title || solution.name"></h4>
|
|
<p class="local-solution-description" x-text="solution.description" x-show="solution.description"></p>
|
|
<template x-if="solution.url">
|
|
<a :href="solution.url" target="_blank" rel="noopener" class="local-solution-link" @click.stop>En savoir plus</a>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
<template x-if="!results.local_solutions?.number">
|
|
<p class="no-result-text">Aucune solution complémentaire disponible pour ce trajet.</p>
|
|
</template>
|
|
{{ with .Params.localSolutionsText }}
|
|
<p class="local-solutions-text">{{ . }}</p>
|
|
{{ end }}
|
|
{{ with .Params.contactCTA.localSolutions }}
|
|
<div class="accordion-cta">
|
|
<p>{{ . }}</p>
|
|
<a href="tel:{{ site.Params.phone | replaceRE " " "" }}" class="accordion-cta-phone">{{ site.Params.phone }}</a>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Colonne droite : Carte MapLibre -->
|
|
<div class="search-results-map">
|
|
<div id="map" x-ref="map" x-init="initMap()"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Gestionnaire de sélection Alpine.js
|
|
function selectionManager() {
|
|
return {
|
|
selectedMode: null,
|
|
selectedIndex: null,
|
|
|
|
selectItem(mode, index, item) {
|
|
this.selectedMode = mode;
|
|
this.selectedIndex = index;
|
|
|
|
// Appeler la fonction d'affichage appropriée
|
|
if (mode === 'transit') {
|
|
window.showJourneyRoute(item, index);
|
|
} else if (mode === 'carpool') {
|
|
window.showCarpoolRoute(item, index);
|
|
} else if (mode === 'solution') {
|
|
window.showSolutionZone(item, index);
|
|
}
|
|
},
|
|
|
|
isSelected(mode, index) {
|
|
return this.selectedMode === mode && this.selectedIndex === index;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Décodeur polyline Google (implémentation native)
|
|
function decodePolyline(encoded, precision) {
|
|
precision = precision || 5;
|
|
const factor = Math.pow(10, precision);
|
|
const len = encoded.length;
|
|
let index = 0;
|
|
let lat = 0;
|
|
let lng = 0;
|
|
const coordinates = [];
|
|
|
|
while (index < len) {
|
|
let b;
|
|
let shift = 0;
|
|
let result = 0;
|
|
|
|
do {
|
|
b = encoded.charCodeAt(index++) - 63;
|
|
result |= (b & 0x1f) << shift;
|
|
shift += 5;
|
|
} while (b >= 0x20);
|
|
|
|
const dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
|
|
lat += dlat;
|
|
|
|
shift = 0;
|
|
result = 0;
|
|
|
|
do {
|
|
b = encoded.charCodeAt(index++) - 63;
|
|
result |= (b & 0x1f) << shift;
|
|
shift += 5;
|
|
} while (b >= 0x20);
|
|
|
|
const dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
|
|
lng += dlng;
|
|
|
|
coordinates.push([lat / factor, lng / factor]);
|
|
}
|
|
|
|
return coordinates;
|
|
}
|
|
|
|
// Compteur pour les layers de legs
|
|
window.journeyLayerIds = [];
|
|
|
|
// Fonction globale pour afficher le tracé sur la carte
|
|
window.showJourneyRoute = function(journey, index) {
|
|
console.log('showJourneyRoute called', index, journey);
|
|
|
|
const map = window.parcoursmobMap;
|
|
if (!map) {
|
|
console.log('Map not ready');
|
|
return;
|
|
}
|
|
|
|
// Supprimer le message d'info covoiturage s'il existe
|
|
const existingInfo = document.querySelector('.carpool-route-info');
|
|
if (existingInfo) existingInfo.remove();
|
|
|
|
// Supprimer les anciens tracés et zones
|
|
window.journeyLayerIds.forEach(id => {
|
|
if (map.getSource(id)) {
|
|
if (map.getLayer(id + '-line')) map.removeLayer(id + '-line');
|
|
if (map.getLayer(id + '-fill')) map.removeLayer(id + '-fill');
|
|
map.removeSource(id);
|
|
}
|
|
});
|
|
window.journeyLayerIds = [];
|
|
|
|
// Convertir le Proxy Alpine en objet simple
|
|
const legs = JSON.parse(JSON.stringify(journey.legs));
|
|
|
|
// Bounds pour ajuster la vue
|
|
const bounds = new maplibregl.LngLatBounds();
|
|
|
|
legs.forEach((leg, legIndex) => {
|
|
console.log('Leg:', leg);
|
|
|
|
const coordinates = [];
|
|
|
|
if (leg.legGeometry && leg.legGeometry.points) {
|
|
console.log('Decoding polyline:', leg.legGeometry.points, 'precision:', leg.legGeometry.precision);
|
|
try {
|
|
const decoded = decodePolyline(leg.legGeometry.points, leg.legGeometry.precision || 5);
|
|
console.log('Decoded points:', decoded.length);
|
|
decoded.forEach(point => {
|
|
const lat = point[0];
|
|
const lng = point[1];
|
|
if (lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
|
|
coordinates.push([lng, lat]);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.error('Erreur décodage polyline:', e);
|
|
}
|
|
} else if (leg.from && leg.to) {
|
|
console.log('Using from/to fallback');
|
|
const fromLng = leg.from.lon || leg.from.lng;
|
|
const fromLat = leg.from.lat;
|
|
const toLng = leg.to.lon || leg.to.lng;
|
|
const toLat = leg.to.lat;
|
|
|
|
if (fromLat && fromLng) coordinates.push([fromLng, fromLat]);
|
|
if (toLat && toLng) coordinates.push([toLng, toLat]);
|
|
}
|
|
|
|
if (coordinates.length < 2) {
|
|
console.log('Not enough coordinates for leg', legIndex);
|
|
return;
|
|
}
|
|
|
|
// Ajouter les coordonnées aux bounds
|
|
coordinates.forEach(coord => bounds.extend(coord));
|
|
|
|
const sourceId = 'journey-leg-' + legIndex;
|
|
window.journeyLayerIds.push(sourceId);
|
|
|
|
// Ajouter la source
|
|
map.addSource(sourceId, {
|
|
type: 'geojson',
|
|
data: {
|
|
type: 'Feature',
|
|
properties: {},
|
|
geometry: {
|
|
type: 'LineString',
|
|
coordinates: coordinates
|
|
}
|
|
}
|
|
});
|
|
|
|
// Déterminer la couleur et le style selon le type de leg
|
|
const isWalk = leg.mode === 'WALK';
|
|
let lineColor = '#888888'; // Gris par défaut pour la marche
|
|
|
|
if (!isWalk) {
|
|
// Utiliser la couleur de la route si disponible
|
|
lineColor = leg.routeColor ? '#' + leg.routeColor : '#283959';
|
|
}
|
|
|
|
// Ajouter le layer
|
|
map.addLayer({
|
|
id: sourceId + '-line',
|
|
type: 'line',
|
|
source: sourceId,
|
|
layout: {
|
|
'line-join': 'round',
|
|
'line-cap': 'round'
|
|
},
|
|
paint: {
|
|
'line-color': lineColor,
|
|
'line-width': isWalk ? 3 : 4,
|
|
'line-dasharray': isWalk ? [2, 2] : [1, 0]
|
|
}
|
|
});
|
|
});
|
|
|
|
// Ajuster la vue si on a des coordonnées
|
|
if (!bounds.isEmpty()) {
|
|
map.fitBounds(bounds, { padding: 50 });
|
|
}
|
|
};
|
|
|
|
// Fonction pour afficher le trajet covoiturage sur la carte
|
|
window.showCarpoolRoute = function(carpool, index) {
|
|
console.log('showCarpoolRoute called', index, carpool);
|
|
|
|
const map = window.parcoursmobMap;
|
|
if (!map) {
|
|
console.log('Map not ready');
|
|
return;
|
|
}
|
|
|
|
// Supprimer les anciens tracés et zones
|
|
window.journeyLayerIds.forEach(id => {
|
|
if (map.getSource(id)) {
|
|
if (map.getLayer(id + '-line')) map.removeLayer(id + '-line');
|
|
if (map.getLayer(id + '-fill')) map.removeLayer(id + '-fill');
|
|
map.removeSource(id);
|
|
}
|
|
});
|
|
window.journeyLayerIds = [];
|
|
|
|
// Supprimer l'ancien message d'info s'il existe
|
|
const existingInfo = document.querySelector('.carpool-route-info');
|
|
if (existingInfo) existingInfo.remove();
|
|
|
|
// Vérifier si une polyline est disponible
|
|
const polylineData = carpool.ocss?.journeyPolyline;
|
|
|
|
if (!polylineData) {
|
|
// Afficher un message d'info au lieu du tracé
|
|
const isMobile = window.matchMedia('(max-width: 1024px)').matches;
|
|
const mapContainer = document.getElementById(isMobile ? 'mobile-map' : 'map');
|
|
if (mapContainer) {
|
|
const infoDiv = document.createElement('div');
|
|
infoDiv.className = 'carpool-route-info';
|
|
infoDiv.textContent = "L'itinéraire exact n'est pas disponible";
|
|
mapContainer.parentElement.appendChild(infoDiv);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Si polyline disponible, décoder et afficher
|
|
const decoded = decodePolyline(polylineData, 5);
|
|
const coordinates = decoded.map(point => [point[1], point[0]]);
|
|
|
|
const sourceId = 'carpool-route';
|
|
window.journeyLayerIds.push(sourceId);
|
|
|
|
map.addSource(sourceId, {
|
|
type: 'geojson',
|
|
data: {
|
|
type: 'Feature',
|
|
properties: {},
|
|
geometry: {
|
|
type: 'LineString',
|
|
coordinates: coordinates
|
|
}
|
|
}
|
|
});
|
|
|
|
map.addLayer({
|
|
id: sourceId + '-line',
|
|
type: 'line',
|
|
source: sourceId,
|
|
layout: {
|
|
'line-join': 'round',
|
|
'line-cap': 'round'
|
|
},
|
|
paint: {
|
|
'line-color': '#00A396',
|
|
'line-width': 4
|
|
}
|
|
});
|
|
|
|
// Ajuster la vue pour montrer le trajet
|
|
const bounds = new maplibregl.LngLatBounds();
|
|
coordinates.forEach(coord => bounds.extend(coord));
|
|
map.fitBounds(bounds, { padding: 50 });
|
|
};
|
|
|
|
// Fonction pour afficher la zone géographique d'une solution locale
|
|
window.showSolutionZone = function(solution, index) {
|
|
console.log('showSolutionZone called', index, solution);
|
|
|
|
const map = window.parcoursmobMap;
|
|
if (!map) {
|
|
console.log('Map not ready');
|
|
return;
|
|
}
|
|
|
|
// Supprimer le message d'info covoiturage s'il existe
|
|
const existingInfo = document.querySelector('.carpool-route-info');
|
|
if (existingInfo) existingInfo.remove();
|
|
|
|
// Supprimer les anciens tracés
|
|
window.journeyLayerIds.forEach(id => {
|
|
if (map.getSource(id)) {
|
|
if (map.getLayer(id + '-line')) map.removeLayer(id + '-line');
|
|
if (map.getLayer(id + '-fill')) map.removeLayer(id + '-fill');
|
|
map.removeSource(id);
|
|
}
|
|
});
|
|
window.journeyLayerIds = [];
|
|
|
|
// Chercher les données géographiques dans la solution
|
|
const geographies = solution.geography || [];
|
|
const bounds = new maplibregl.LngLatBounds();
|
|
let hasGeometry = false;
|
|
|
|
geographies.forEach((geo, geoIndex) => {
|
|
if (geo.geography && geo.geography.geometry) {
|
|
hasGeometry = true;
|
|
const sourceId = 'solution-zone-' + index + '-' + geoIndex;
|
|
window.journeyLayerIds.push(sourceId);
|
|
|
|
map.addSource(sourceId, {
|
|
type: 'geojson',
|
|
data: geo.geography
|
|
});
|
|
|
|
// Ajouter le remplissage
|
|
map.addLayer({
|
|
id: sourceId + '-fill',
|
|
type: 'fill',
|
|
source: sourceId,
|
|
paint: {
|
|
'fill-color': '#F39200',
|
|
'fill-opacity': 0.2
|
|
}
|
|
});
|
|
|
|
// Ajouter le contour
|
|
map.addLayer({
|
|
id: sourceId + '-line',
|
|
type: 'line',
|
|
source: sourceId,
|
|
paint: {
|
|
'line-color': '#F39200',
|
|
'line-width': 2
|
|
}
|
|
});
|
|
|
|
// Étendre les bounds avec les coordonnées
|
|
const coords = geo.geography.geometry.coordinates;
|
|
if (geo.geography.geometry.type === 'Polygon') {
|
|
coords[0].forEach(coord => bounds.extend(coord));
|
|
} else if (geo.geography.geometry.type === 'MultiPolygon') {
|
|
coords.forEach(polygon => {
|
|
polygon[0].forEach(coord => bounds.extend(coord));
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
if (hasGeometry && !bounds.isEmpty()) {
|
|
map.fitBounds(bounds, { padding: 50 });
|
|
}
|
|
};
|
|
|
|
// Fonctions de formatage pour les trajets
|
|
function formatTime(dateString) {
|
|
if (!dateString) return '';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
function formatJourneyDate(dateString) {
|
|
if (!dateString) return '';
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' });
|
|
}
|
|
|
|
function formatDuration(seconds) {
|
|
if (!seconds) return '';
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
if (hours > 0) {
|
|
return `${hours}h${minutes.toString().padStart(2, '0')}m`;
|
|
}
|
|
return `${minutes}m`;
|
|
}
|
|
|
|
function formatDistance(meters) {
|
|
if (!meters) return '';
|
|
if (meters >= 1000) {
|
|
const km = (meters / 1000).toFixed(1).replace('.', ',');
|
|
return `${km} kilomètres`;
|
|
}
|
|
return `${Math.round(meters)} mètres`;
|
|
}
|
|
|
|
function formatCarpoolPrice(amount) {
|
|
if (!amount && amount !== 0) return '';
|
|
return amount.toFixed(2).replace('.', ',') + ' €';
|
|
}
|
|
|
|
function formatCarpoolDate(timestamp) {
|
|
if (!timestamp) return '';
|
|
// Le timestamp OCSS est en secondes, pas en millisecondes
|
|
const date = new Date(timestamp * 1000);
|
|
return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' }) +
|
|
' à ' + date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
|
|
function initMap() {
|
|
const data = window.__PARCOURSMOB_DATA__ || {};
|
|
const departure = data.departure;
|
|
const destination = data.destination;
|
|
|
|
if (!departure || !destination) return;
|
|
|
|
const depCoords = departure.geometry.coordinates;
|
|
const destCoords = destination.geometry.coordinates;
|
|
|
|
// Calculer les bounds
|
|
const bounds = [
|
|
[Math.min(depCoords[0], destCoords[0]), Math.min(depCoords[1], destCoords[1])],
|
|
[Math.max(depCoords[0], destCoords[0]), Math.max(depCoords[1], destCoords[1])]
|
|
];
|
|
|
|
// Initialiser le protocole PMTiles
|
|
const protocol = new pmtiles.Protocol();
|
|
maplibregl.addProtocol("pmtiles", protocol.tile);
|
|
|
|
// Fonction pour créer une carte dans un conteneur donné
|
|
function createMapInContainer(containerId) {
|
|
const container = document.getElementById(containerId);
|
|
if (!container) return null;
|
|
|
|
const map = new maplibregl.Map({
|
|
container: containerId,
|
|
style: '/maps/protomaps-light.json',
|
|
bounds: bounds,
|
|
fitBoundsOptions: { padding: 50 }
|
|
});
|
|
|
|
map.addControl(new maplibregl.NavigationControl(), 'top-right');
|
|
|
|
// Marqueur départ (secondary/vert)
|
|
const departureEl = document.createElement('div');
|
|
departureEl.className = 'map-marker map-marker-departure';
|
|
departureEl.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18" height="18">
|
|
<path fill-rule="evenodd" d="M11.54 22.351l.07.04.028.016a.76.76 0 00.723 0l.028-.015.071-.041a16.975 16.975 0 001.144-.742 19.58 19.58 0 002.683-2.282c1.944-1.99 3.963-4.98 3.963-8.827a8.25 8.25 0 00-16.5 0c0 3.846 2.02 6.837 3.963 8.827a19.58 19.58 0 002.682 2.282 16.975 16.975 0 001.145.742zM12 13.5a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
|
|
</svg>
|
|
`;
|
|
new maplibregl.Marker({ element: departureEl })
|
|
.setLngLat(depCoords)
|
|
.setPopup(new maplibregl.Popup().setHTML(`<strong>Départ</strong><br>${departure.properties.label}`))
|
|
.addTo(map);
|
|
|
|
// Marqueur arrivée (highlight/orange)
|
|
const arrivalEl = document.createElement('div');
|
|
arrivalEl.className = 'map-marker map-marker-arrival';
|
|
arrivalEl.innerHTML = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18" height="18">
|
|
<path fill-rule="evenodd" d="M3 2.25a.75.75 0 01.75.75v.54l1.838-.46a9.75 9.75 0 016.725.738l.108.054a8.25 8.25 0 005.58.652l3.109-.732a.75.75 0 01.917.81 47.784 47.784 0 00.005 10.337.75.75 0 01-.574.812l-3.114.733a9.75 9.75 0 01-6.594-.77l-.108-.054a8.25 8.25 0 00-5.69-.625l-2.202.55V21a.75.75 0 01-1.5 0V3A.75.75 0 013 2.25z" clip-rule="evenodd" />
|
|
</svg>
|
|
`;
|
|
new maplibregl.Marker({ element: arrivalEl })
|
|
.setLngLat(destCoords)
|
|
.setPopup(new maplibregl.Popup().setHTML(`<strong>Arrivée</strong><br>${destination.properties.label}`))
|
|
.addTo(map);
|
|
|
|
return map;
|
|
}
|
|
|
|
// Déterminer si on est en mobile
|
|
const isMobile = window.matchMedia('(max-width: 1024px)').matches;
|
|
|
|
// Créer la carte dans le bon conteneur
|
|
if (isMobile) {
|
|
// En mobile, créer uniquement la carte mobile
|
|
const mobileMap = createMapInContainer('mobile-map');
|
|
if (mobileMap) {
|
|
window.parcoursmobMap = mobileMap;
|
|
}
|
|
} else {
|
|
// En desktop, créer uniquement la carte desktop
|
|
const desktopMap = createMapInContainer('map');
|
|
if (desktopMap) {
|
|
window.parcoursmobMap = desktopMap;
|
|
}
|
|
}
|
|
|
|
// Écouter les changements de taille d'écran pour recréer la carte si nécessaire
|
|
const mediaQuery = window.matchMedia('(max-width: 1024px)');
|
|
mediaQuery.addEventListener('change', (e) => {
|
|
// Supprimer l'ancienne carte
|
|
if (window.parcoursmobMap) {
|
|
window.parcoursmobMap.remove();
|
|
window.parcoursmobMap = null;
|
|
}
|
|
|
|
// Créer la nouvelle carte dans le bon conteneur
|
|
if (e.matches) {
|
|
const mobileMap = createMapInContainer('mobile-map');
|
|
if (mobileMap) {
|
|
window.parcoursmobMap = mobileMap;
|
|
}
|
|
} else {
|
|
const desktopMap = createMapInContainer('map');
|
|
if (desktopMap) {
|
|
window.parcoursmobMap = desktopMap;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|