Compare commits
10 Commits
45a96f618b
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
681d5da4d1
|
|||
|
65ccd2daaf
|
|||
|
7e6b670847
|
|||
|
14b7405961
|
|||
|
980a3dbe0c
|
|||
|
e693713267
|
|||
|
3ff1b82b85
|
|||
|
f42b09ebab
|
|||
|
908bf5f0c2
|
|||
|
fa4ef6320a
|
4
.gitignore
vendored
@ -49,5 +49,5 @@ next-env.d.ts
|
||||
# data storage (ne pas commit les données clients)
|
||||
/data/clients.json
|
||||
|
||||
# APK
|
||||
/dist/*.apk
|
||||
# APK (commenté pour permettre le téléchargement depuis le dépôt)
|
||||
# /dist/*.apk
|
||||
|
||||
@ -44,7 +44,7 @@ export default function Logo({ size = 120, className = "" }: LogoProps) {
|
||||
alt="Relais Marama - Fakarava"
|
||||
width={size}
|
||||
height={size}
|
||||
className="object-contain"
|
||||
className="object-contain dark:invert"
|
||||
style={{ maxWidth: `${size}px`, maxHeight: `${size}px` }}
|
||||
/>
|
||||
<p className="text-primary font-semibold mt-2" style={{ fontSize: `${size * 0.15}px` }}>
|
||||
|
||||
@ -1,35 +1,177 @@
|
||||
import { Cloud, Sun } from "lucide-react";
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Cloud, Sun, CloudRain, Wind, Droplets, Loader2, Clock } from "lucide-react";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
|
||||
interface WeatherData {
|
||||
temperature: number;
|
||||
condition: string;
|
||||
windSpeed: number;
|
||||
humidity: number;
|
||||
weatherCode: number;
|
||||
}
|
||||
|
||||
// Coordonnées de Fakarava (Rotoava)
|
||||
const FAKARAVA_LAT = -16.3167;
|
||||
const FAKARAVA_LON = -145.6167;
|
||||
|
||||
// Codes météo Open-Meteo vers descriptions
|
||||
const getWeatherCondition = (code: number): { text: string; icon: React.ReactNode } => {
|
||||
// Codes Open-Meteo WMO Weather interpretation codes
|
||||
if (code === 0) {
|
||||
return { text: "Ciel dégagé", icon: <Sun className="h-16 w-16 text-yellow-400" /> };
|
||||
} else if (code <= 3) {
|
||||
return { text: "Partiellement nuageux", icon: <Cloud className="h-16 w-16 text-gray-400" /> };
|
||||
} else if (code <= 48) {
|
||||
return { text: "Nuageux", icon: <Cloud className="h-16 w-16 text-gray-500" /> };
|
||||
} else if (code <= 55) {
|
||||
return { text: "Brouillard", icon: <Cloud className="h-16 w-16 text-gray-400" /> };
|
||||
} else if (code <= 67) {
|
||||
return { text: "Pluie", icon: <CloudRain className="h-16 w-16 text-blue-400" /> };
|
||||
} else if (code <= 77) {
|
||||
return { text: "Neige", icon: <CloudRain className="h-16 w-16 text-gray-300" /> };
|
||||
} else if (code <= 82) {
|
||||
return { text: "Averses", icon: <CloudRain className="h-16 w-16 text-blue-500" /> };
|
||||
} else if (code <= 86) {
|
||||
return { text: "Averses de neige", icon: <CloudRain className="h-16 w-16 text-gray-300" /> };
|
||||
} else {
|
||||
return { text: "Orage", icon: <CloudRain className="h-16 w-16 text-purple-500" /> };
|
||||
}
|
||||
};
|
||||
|
||||
export default function WeatherWidget() {
|
||||
const [weather, setWeather] = useState<WeatherData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [localTime, setLocalTime] = useState<string>("");
|
||||
|
||||
// Mise à jour de l'heure locale chaque seconde
|
||||
useEffect(() => {
|
||||
const updateTime = () => {
|
||||
const now = new Date();
|
||||
// Fuseau horaire de Tahiti (UTC-10)
|
||||
const tahitiTime = new Date(now.toLocaleString("en-US", { timeZone: "Pacific/Tahiti" }));
|
||||
const hours = tahitiTime.getHours().toString().padStart(2, "0");
|
||||
const minutes = tahitiTime.getMinutes().toString().padStart(2, "0");
|
||||
setLocalTime(`${hours}:${minutes}`);
|
||||
};
|
||||
|
||||
updateTime();
|
||||
const timeInterval = setInterval(updateTime, 1000);
|
||||
return () => clearInterval(timeInterval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchWeather = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// API Open-Meteo (gratuit, pas de clé API nécessaire)
|
||||
const response = await fetch(
|
||||
`https://api.open-meteo.com/v1/forecast?latitude=${FAKARAVA_LAT}&longitude=${FAKARAVA_LON}¤t=temperature_2m,relative_humidity_2m,weather_code,wind_speed_10m&wind_speed_unit=kmh&timezone=Pacific/Tahiti`
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Erreur lors de la récupération de la météo");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const current = data.current;
|
||||
|
||||
setWeather({
|
||||
temperature: Math.round(current.temperature_2m),
|
||||
condition: getWeatherCondition(current.weather_code).text,
|
||||
windSpeed: Math.round(current.wind_speed_10m),
|
||||
humidity: current.relative_humidity_2m,
|
||||
weatherCode: current.weather_code,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Erreur météo:", err);
|
||||
setError("Impossible de charger la météo");
|
||||
// Valeurs par défaut en cas d'erreur
|
||||
setWeather({
|
||||
temperature: 28,
|
||||
condition: "Ensoleillé",
|
||||
windSpeed: 15,
|
||||
humidity: 75,
|
||||
weatherCode: 0,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchWeather();
|
||||
|
||||
// Rafraîchir toutes les heures
|
||||
const interval = setInterval(fetchWeather, 3600000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card className="bg-gradient-to-br from-primary/10 to-secondary">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Sun className="h-6 w-6 text-primary" />
|
||||
Météo - Fakarava
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!weather) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const weatherInfo = getWeatherCondition(weather.weatherCode);
|
||||
|
||||
return (
|
||||
<Card className="bg-gradient-to-br from-primary/10 to-secondary">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Sun className="h-6 w-6 text-primary" />
|
||||
Météo
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sun className="h-6 w-6 text-primary" />
|
||||
Météo - Fakarava
|
||||
</div>
|
||||
{localTime && (
|
||||
<div className="flex items-center gap-1 text-sm font-normal text-gray-600 dark:text-gray-400">
|
||||
<Clock className="h-4 w-4" />
|
||||
{localTime}
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-3xl font-bold text-primary">28°C</p>
|
||||
<p className="text-gray-600 mt-1">Ensoleillé</p>
|
||||
</div>
|
||||
<div className="text-6xl">
|
||||
<Sun className="h-16 w-16 text-yellow-400" />
|
||||
<p className="text-3xl font-bold text-primary">{weather.temperature}°C</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mt-1">{weather.condition}</p>
|
||||
{error && (
|
||||
<p className="text-xs text-orange-500 mt-1">Données en cache</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-6xl">{weatherInfo.icon}</div>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-4 text-sm text-gray-600">
|
||||
<div>
|
||||
<span className="font-semibold">Vent:</span> 15 km/h
|
||||
<div className="mt-4 flex gap-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className="flex items-center gap-1">
|
||||
<Wind className="h-4 w-4" />
|
||||
<span className="font-semibold">Vent:</span> {weather.windSpeed} km/h
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">Humidité:</span> 75%
|
||||
<div className="flex items-center gap-1">
|
||||
<Droplets className="h-4 w-4" />
|
||||
<span className="font-semibold">Humidité:</span> {weather.humidity}%
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { MapPin, ExternalLink } from "lucide-react";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@ -10,6 +11,9 @@ interface PlaceCardProps {
|
||||
}
|
||||
|
||||
export default function PlaceCard({ place }: PlaceCardProps) {
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const hasValidImage = place.image && place.image !== "";
|
||||
|
||||
const handleOpenMaps = () => {
|
||||
let url: string;
|
||||
if (place.gmapLink && place.gmapLink !== "LIEN_GOOGLE_MAPS_A_INSERER") {
|
||||
@ -22,10 +26,24 @@ export default function PlaceCard({ place }: PlaceCardProps) {
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden">
|
||||
<div className="relative h-48 bg-gradient-to-br from-primary/20 to-secondary">
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<MapPin className="h-16 w-16 text-primary/30" />
|
||||
</div>
|
||||
<div className="relative h-48 bg-gradient-to-br from-primary/20 to-secondary overflow-hidden">
|
||||
{hasValidImage && !imageError ? (
|
||||
<img
|
||||
src={place.image}
|
||||
alt={place.name}
|
||||
className="w-full h-full object-cover"
|
||||
style={{
|
||||
imageRendering: 'auto',
|
||||
} as React.CSSProperties}
|
||||
loading="lazy"
|
||||
fetchPriority="high"
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<MapPin className="h-16 w-16 text-primary/30" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
|
||||
BIN
dist/compagnon-lagon-beta.apk
vendored
@ -7,7 +7,7 @@
|
||||
"description": "LE lieu pour déjeuner les pieds dans l'eau. Poisson cru légendaire. Le cadre est inoubliable.",
|
||||
"keywords": ["Lagon", "Poisson Cru", "Ambiance", "Incontournable"],
|
||||
"contact": "+689 40 93 40 15",
|
||||
"image": "/placeholder-restaurant.jpg",
|
||||
"image": "/images/restaurant-requin-dormeur.jpg",
|
||||
"location": {
|
||||
"lat": -16.3167,
|
||||
"lng": -145.6167,
|
||||
@ -23,7 +23,7 @@
|
||||
"description": "Super vue sur le lagon, parfait pour un déjeuner simple et bon. Leur Fish Burger est très apprécié.",
|
||||
"keywords": ["Décontracté", "Vue", "Fish Burger", "Poisson Grillé"],
|
||||
"contact": "+689 40 98 43 97",
|
||||
"image": "/placeholder-restaurant.jpg",
|
||||
"image": "/images/restaurant-kori-kori.jpg",
|
||||
"location": {
|
||||
"lat": -16.3177,
|
||||
"lng": -145.6177,
|
||||
@ -38,7 +38,7 @@
|
||||
"type": "Plage",
|
||||
"description": "La plage la plus photogénique du Nord de Fakarava. Vous y trouverez le célèbre cocotier penché, parfait pour une photo \"carte postale\". Idéal pour le farniente et la baignade tranquille.",
|
||||
"keywords": ["Photogénique", "Cocotier", "Baignade", "Farniente"],
|
||||
"image": "/placeholder-beach.jpg",
|
||||
"image": "/images/plage-cocotier-pk9.jpg",
|
||||
"location": {
|
||||
"lat": -16.3187,
|
||||
"lng": -145.6187,
|
||||
@ -54,7 +54,7 @@
|
||||
"type": "Point de vue",
|
||||
"description": "Un peu plus loin après le PK 9, le phare n'est pas une plage en soi, mais le lieu offre une vue unique sur l'Océan Pacifique (côté \"océan\" et non lagon). Ambiance de \"bout du monde\".",
|
||||
"keywords": ["Phare", "Vue panoramique", "Océan", "Point de vue"],
|
||||
"image": "/placeholder-beach.jpg",
|
||||
"image": "/images/phare-topaka.jpg",
|
||||
"location": {
|
||||
"lat": -16.3197,
|
||||
"lng": -145.6197,
|
||||
@ -70,7 +70,7 @@
|
||||
"type": "Spot de snorkeling",
|
||||
"description": "L'une des plus grandes passes de Polynésie. On n'y va pas pour la plage, mais pour l'activité de snorkeling en dérivante qui y est spectaculaire (à faire avec un club). Le paysage est impressionnant.",
|
||||
"keywords": ["Passe", "Snorkeling", "Dérivante", "Spectaculaire"],
|
||||
"image": "/placeholder-beach.jpg",
|
||||
"image": "/images/passe-nord-snorkeling.jpg",
|
||||
"location": {
|
||||
"lat": -16.3207,
|
||||
"lng": -145.6207,
|
||||
@ -86,7 +86,7 @@
|
||||
"type": "Plage exceptionnelle",
|
||||
"description": "L'excursion la plus célèbre de Fakarava. Il s'agit d'une plage unique de sable aux nuances rosées, à la pointe Sud de l'atoll.",
|
||||
"keywords": ["Sable rose", "Exceptionnel", "Passe Sud", "Excursion"],
|
||||
"image": "/placeholder-beach.jpg",
|
||||
"image": "/images/sables-roses.jpg",
|
||||
"location": {
|
||||
"lat": -16.3217,
|
||||
"lng": -145.6217,
|
||||
@ -102,7 +102,7 @@
|
||||
"type": "Plage privée",
|
||||
"description": "N'hésitez pas à mettre en avant votre propre plage et ponton ! Idéal pour un snorkeling facile et sécurisé.",
|
||||
"keywords": ["Plage privée", "Ponton", "Snorkeling", "Sécurisé"],
|
||||
"image": "/placeholder-beach.jpg",
|
||||
"image": "/images/plage-pension.jpg",
|
||||
"location": {
|
||||
"lat": -16.3227,
|
||||
"lng": -145.6227,
|
||||
@ -113,18 +113,18 @@
|
||||
},
|
||||
{
|
||||
"id": "fakarava-8",
|
||||
"name": "Magasin Rotoava",
|
||||
"name": "Tumoana Markets",
|
||||
"category": "epiceries",
|
||||
"type": "Épicerie principale",
|
||||
"description": "L'épicerie principale du village, idéale pour le ravitaillement. Bien achalandée avec les produits essentiels.",
|
||||
"keywords": ["Épicerie", "Ravitaillement", "Village", "Principal"],
|
||||
"image": "/placeholder-store.jpg",
|
||||
"image": "/images/epicerie-tumoana.jpg",
|
||||
"location": {
|
||||
"lat": -16.3237,
|
||||
"lng": -145.6237,
|
||||
"address": "Cœur du village de Rotoava"
|
||||
"lat": -16.05578,
|
||||
"lng": -145.620504,
|
||||
"address": "Rotoava"
|
||||
},
|
||||
"gmapLink": "https://www.google.com/maps/search/?api=1&query=Magasin+Rotoava+Fakarava",
|
||||
"gmapLink": "https://www.google.com/maps/place/Tumoana+Markets/@-16.0587525,-145.6213777",
|
||||
"horaires": "Lundi au Samedi : 7h00 - 11h30 et 15h00 - 17h30. (Fermé les jours fériés.)",
|
||||
"conseil": "Idéal pour le ravitaillement principal. Pensez à y aller avant midi !"
|
||||
},
|
||||
@ -135,13 +135,13 @@
|
||||
"type": "Snack / Dépannage",
|
||||
"description": "Souvent mentionné pour sa cuisine, il est aussi un point de vente pour quelques nécessités.",
|
||||
"keywords": ["Snack", "Dépannage", "Cuisine", "Nécessités"],
|
||||
"image": "/placeholder-store.jpg",
|
||||
"image": "/images/snack-chez-elda.jpg",
|
||||
"location": {
|
||||
"lat": -16.3247,
|
||||
"lng": -145.6247,
|
||||
"address": "Rotoava"
|
||||
},
|
||||
"gmapLink": "https://www.google.com/maps/search/?api=1&query=Snack+Chez+Elda+Fakarava",
|
||||
"gmapLink": "https://www.google.com/maps/place/Snack+Elda/@-16.0547419,-145.6207965",
|
||||
"horaires": "Horaires variables.",
|
||||
"conseil": "Peut dépanner en cas de besoin en dehors des heures de pointe."
|
||||
},
|
||||
@ -152,7 +152,7 @@
|
||||
"type": "Épiceries locales",
|
||||
"description": "Les autres magasins sont plus petits et moins achalandés, mais pratiques si vous êtes loin du centre.",
|
||||
"keywords": ["Petites épiceries", "Locales", "Dépannage"],
|
||||
"image": "/placeholder-store.jpg",
|
||||
"image": "/images/epicerie-locale.jpg",
|
||||
"location": {
|
||||
"lat": -16.3257,
|
||||
"lng": -145.6257,
|
||||
|
||||
BIN
public/images/epicerie-locale.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
public/images/epicerie-rotoava.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/epicerie-tumoana.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/passe-nord-snorkeling.jpg
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/images/phare-topaka.jpg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/images/plage-cocotier-pk9.jpg
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
public/images/plage-pension.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/images/restaurant-kori-kori.jpg
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
public/images/restaurant-requin-dormeur.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/images/sables-roses.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
public/images/snack-chez-elda.jpg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
91
scripts/README-IMAGES-HD.md
Normal file
@ -0,0 +1,91 @@
|
||||
# Guide pour télécharger les images en haute résolution
|
||||
|
||||
Ce guide explique comment télécharger les images Google Maps en haute résolution pour améliorer la qualité d'affichage dans l'application.
|
||||
|
||||
## Méthode 1 : Utiliser le script Node.js (Recommandé)
|
||||
|
||||
### Étape 1 : Obtenir les URLs d'images depuis Google Maps
|
||||
|
||||
Pour chaque lieu, suivez ces étapes :
|
||||
|
||||
1. **Ouvrez le lien Google Maps** du lieu dans votre navigateur
|
||||
2. **Faites défiler** jusqu'à voir les photos du lieu
|
||||
3. **Cliquez sur une photo** pour l'agrandir
|
||||
4. **Faites un clic droit** sur l'image agrandie > **Inspecter l'élément**
|
||||
5. **Trouvez l'URL de l'image** dans le code HTML (généralement sur `googleusercontent.com`)
|
||||
6. **Copiez l'URL complète** de l'image
|
||||
|
||||
### Étape 2 : Modifier l'URL pour haute résolution
|
||||
|
||||
Les URLs Google Maps contiennent des paramètres de taille comme `w=203-h=152`. Pour obtenir une meilleure résolution :
|
||||
|
||||
- Remplacez `w=203` par `w=1600` (ou `w=2048` pour encore plus de qualité)
|
||||
- Remplacez `h=152` par `h=1200` (ou `h=1536` pour encore plus de qualité)
|
||||
|
||||
**Exemple :**
|
||||
```
|
||||
URL originale: https://lh3.googleusercontent.com/...w=203-h=152-k-no
|
||||
URL haute rés: https://lh3.googleusercontent.com/...w=1600-h=1200-k-no
|
||||
```
|
||||
|
||||
### Étape 3 : Configurer le script
|
||||
|
||||
Éditez le fichier `scripts/download-high-res-images.js` et ajoutez vos URLs dans le tableau `IMAGES_TO_DOWNLOAD` :
|
||||
|
||||
```javascript
|
||||
const IMAGES_TO_DOWNLOAD = [
|
||||
{
|
||||
name: "Snack du Requin Dormeur",
|
||||
url: "https://lh3.googleusercontent.com/...w=1600-h=1200-k-no",
|
||||
outputFile: "restaurant-requin-dormeur.jpg"
|
||||
},
|
||||
{
|
||||
name: "Snack Kori Kori",
|
||||
url: "https://lh3.googleusercontent.com/...w=1600-h=1200-k-no",
|
||||
outputFile: "restaurant-kori-kori.jpg"
|
||||
},
|
||||
// ... ajoutez les autres images
|
||||
];
|
||||
```
|
||||
|
||||
### Étape 4 : Exécuter le script
|
||||
|
||||
```bash
|
||||
node scripts/download-high-res-images.js
|
||||
```
|
||||
|
||||
Le script téléchargera toutes les images configurées dans le dossier `public/images/`.
|
||||
|
||||
## Méthode 2 : Téléchargement manuel
|
||||
|
||||
Si vous préférez télécharger manuellement :
|
||||
|
||||
1. Suivez les étapes 1 et 2 ci-dessus pour obtenir les URLs en haute résolution
|
||||
2. Ouvrez chaque URL dans votre navigateur
|
||||
3. Faites un clic droit > **Enregistrer l'image sous...**
|
||||
4. Enregistrez dans `public/images/` avec le bon nom de fichier
|
||||
|
||||
## Images à télécharger
|
||||
|
||||
Voici la liste des images qui proviennent de Google Maps et qui peuvent être améliorées :
|
||||
|
||||
- `restaurant-requin-dormeur.jpg` - Snack du Requin Dormeur
|
||||
- `restaurant-kori-kori.jpg` - Snack Kori Kori
|
||||
- `plage-cocotier-pk9.jpg` - Plage PK 9
|
||||
- `phare-topaka.jpg` - Ancien Phare de Topaka
|
||||
- `epicerie-tumoana.jpg` - Tumoana Markets
|
||||
- `snack-chez-elda.jpg` - Snack Chez Elda
|
||||
|
||||
## Résolution recommandée
|
||||
|
||||
- **Minimum** : 1200x800 pixels
|
||||
- **Recommandé** : 1600x1200 pixels
|
||||
- **Maximum** : 2048x1536 pixels (pour éviter des fichiers trop lourds)
|
||||
|
||||
## Notes importantes
|
||||
|
||||
- Les URLs Google Maps peuvent expirer après un certain temps
|
||||
- Si une URL ne fonctionne plus, vous devrez la récupérer à nouveau depuis Google Maps
|
||||
- Les images haute résolution seront plus lourdes, mais la qualité d'affichage sera bien meilleure
|
||||
- Le composant `PlaceCard` a été optimisé pour un meilleur rendu des images
|
||||
|
||||
153
scripts/download-high-res-images.js
Executable file
@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Script pour télécharger les images Google Maps en haute résolution
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/download-high-res-images.js
|
||||
*
|
||||
* Ce script télécharge les images depuis les URLs Google Maps en modifiant
|
||||
* les paramètres de taille pour obtenir une meilleure résolution.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
|
||||
const IMAGES_DIR = path.join(__dirname, '..', 'public', 'images');
|
||||
|
||||
// Configuration des images à télécharger
|
||||
// Format: { name, url, outputFile }
|
||||
// Les URLs doivent être les URLs directes des images Google Maps (googleusercontent.com)
|
||||
// Pour obtenir ces URLs:
|
||||
// 1. Ouvrez le lien Google Maps dans un navigateur
|
||||
// 2. Faites un clic droit sur l'image > Inspecter l'élément
|
||||
// 3. Copiez l'URL de l'image (généralement sur googleusercontent.com)
|
||||
// 4. Modifiez les paramètres w=XXX par w=1600 et h=YYY par h=1200 dans l'URL
|
||||
const IMAGES_TO_DOWNLOAD = [
|
||||
// Exemple (à remplacer par les vraies URLs):
|
||||
// {
|
||||
// name: "Snack du Requin Dormeur",
|
||||
// url: "https://lh3.googleusercontent.com/...w=1600-h=1200-k-no",
|
||||
// outputFile: "restaurant-requin-dormeur.jpg"
|
||||
// },
|
||||
];
|
||||
|
||||
/**
|
||||
* Télécharge une image depuis une URL
|
||||
*/
|
||||
function downloadImage(url, outputPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
|
||||
const file = fs.createWriteStream(outputPath);
|
||||
|
||||
protocol.get(url, (response) => {
|
||||
if (response.statusCode === 200) {
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
resolve();
|
||||
});
|
||||
} else if (response.statusCode === 301 || response.statusCode === 302) {
|
||||
// Suivre les redirections
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
downloadImage(response.headers.location, outputPath)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
} else {
|
||||
file.close();
|
||||
fs.unlinkSync(outputPath);
|
||||
reject(new Error(`Erreur HTTP ${response.statusCode}`));
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
file.close();
|
||||
if (fs.existsSync(outputPath)) {
|
||||
fs.unlinkSync(outputPath);
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un fichier est une image valide
|
||||
*/
|
||||
function isValidImage(filePath) {
|
||||
try {
|
||||
const stats = fs.statSync(filePath);
|
||||
if (stats.size === 0) return false;
|
||||
|
||||
// Vérifier l'extension
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return ['.jpg', '.jpeg', '.png', '.webp'].includes(ext);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction principale
|
||||
*/
|
||||
async function main() {
|
||||
console.log('========================================');
|
||||
console.log('📥 Téléchargement images haute résolution');
|
||||
console.log('========================================\n');
|
||||
|
||||
// Créer le dossier images s'il n'existe pas
|
||||
if (!fs.existsSync(IMAGES_DIR)) {
|
||||
fs.mkdirSync(IMAGES_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
if (IMAGES_TO_DOWNLOAD.length === 0) {
|
||||
console.log('⚠️ Aucune URL d\'image configurée dans le script.\n');
|
||||
console.log('Pour télécharger des images en haute résolution:');
|
||||
console.log('1. Ouvrez le lien Google Maps dans un navigateur');
|
||||
console.log('2. Faites un clic droit sur l\'image > Inspecter l\'élément');
|
||||
console.log('3. Copiez l\'URL de l\'image (généralement sur googleusercontent.com)');
|
||||
console.log('4. Modifiez les paramètres w=XXX par w=1600 et h=YYY par h=1200');
|
||||
console.log('5. Ajoutez l\'URL dans le tableau IMAGES_TO_DOWNLOAD du script\n');
|
||||
console.log('Exemple d\'URL Google Maps image:');
|
||||
console.log('https://lh3.googleusercontent.com/gps-cs-s/...w=1600-h=1200-k-no\n');
|
||||
return;
|
||||
}
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const image of IMAGES_TO_DOWNLOAD) {
|
||||
const outputPath = path.join(IMAGES_DIR, image.outputFile);
|
||||
|
||||
console.log(`📸 Téléchargement: ${image.name}`);
|
||||
console.log(` URL: ${image.url.substring(0, 80)}...`);
|
||||
|
||||
try {
|
||||
await downloadImage(image.url, outputPath);
|
||||
|
||||
if (isValidImage(outputPath)) {
|
||||
const stats = fs.statSync(outputPath);
|
||||
const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
||||
console.log(` ✅ Téléchargé: ${image.outputFile} (${sizeMB} MB)\n`);
|
||||
successCount++;
|
||||
} else {
|
||||
console.log(` ❌ Fichier invalide: ${image.outputFile}\n`);
|
||||
failCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` ❌ Erreur: ${error.message}\n`);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('========================================');
|
||||
console.log('✅ Téléchargement terminé');
|
||||
console.log('========================================');
|
||||
console.log(`✅ Succès: ${successCount}`);
|
||||
console.log(`❌ Échecs: ${failCount}\n`);
|
||||
}
|
||||
|
||||
// Exécuter le script
|
||||
main().catch(console.error);
|
||||
|
||||
113
scripts/download-high-res-images.sh
Executable file
@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script pour télécharger les images Google Maps en haute résolution
|
||||
# Usage: ./scripts/download-high-res-images.sh
|
||||
|
||||
set -e
|
||||
|
||||
IMAGES_DIR="public/images"
|
||||
RESOLUTION="1600x1200" # Haute résolution
|
||||
|
||||
echo "=========================================="
|
||||
echo "📥 Téléchargement images haute résolution"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Fonction pour télécharger une image Google Maps en haute résolution
|
||||
download_google_maps_image() {
|
||||
local image_url=$1
|
||||
local output_file=$2
|
||||
local place_name=$3
|
||||
|
||||
echo "📸 Téléchargement: $place_name"
|
||||
|
||||
# Extraire l'URL de l'image depuis l'URL Google Maps
|
||||
# Les URLs Google Maps contiennent souvent des paramètres w= et h= pour la taille
|
||||
# On va remplacer ces paramètres par des valeurs plus élevées
|
||||
|
||||
# Si l'URL contient déjà des paramètres de taille, on les remplace
|
||||
if [[ $image_url == *"w="* ]] && [[ $image_url == *"h="* ]]; then
|
||||
# Remplacer w=XXX par w=1600 et h=YYY par h=1200
|
||||
high_res_url=$(echo "$image_url" | sed 's/w=[0-9]*/w=1600/g' | sed 's/h=[0-9]*/h=1200/g')
|
||||
else
|
||||
# Si pas de paramètres de taille, on essaie d'ajouter des paramètres
|
||||
# Note: Google Maps peut ne pas accepter tous les paramètres, donc on utilise l'URL telle quelle
|
||||
high_res_url="$image_url"
|
||||
fi
|
||||
|
||||
# Télécharger l'image
|
||||
if curl -L -f -s "$high_res_url" -o "$output_file" 2>/dev/null; then
|
||||
# Vérifier que le fichier est valide
|
||||
if file "$output_file" | grep -q "image"; then
|
||||
local size=$(du -h "$output_file" | cut -f1)
|
||||
echo " ✅ Téléchargé: $output_file ($size)"
|
||||
return 0
|
||||
else
|
||||
echo " ❌ Fichier invalide: $output_file"
|
||||
rm -f "$output_file"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Échec du téléchargement (URL peut-être invalide ou expirée)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Liste des images Google Maps à télécharger en haute résolution
|
||||
# Format: "URL_IMAGE|FICHIER_SORTIE|NOM_LIEU"
|
||||
|
||||
declare -a IMAGES=(
|
||||
# Ces URLs doivent être extraites depuis les liens Google Maps
|
||||
# Pour obtenir l'URL de l'image, il faut:
|
||||
# 1. Ouvrir le lien Google Maps dans un navigateur
|
||||
# 2. Faire un clic droit sur l'image > Inspecter l'élément
|
||||
# 3. Copier l'URL de l'image (généralement sur googleusercontent.com)
|
||||
# 4. Modifier les paramètres w= et h= pour augmenter la résolution
|
||||
|
||||
# Exemple (à remplacer par les vraies URLs):
|
||||
# "https://lh3.googleusercontent.com/...w=1600-h=1200...|restaurant-requin-dormeur.jpg|Snack du Requin Dormeur"
|
||||
)
|
||||
|
||||
if [ ${#IMAGES[@]} -eq 0 ]; then
|
||||
echo "⚠️ Aucune URL d'image configurée dans le script."
|
||||
echo ""
|
||||
echo "Pour télécharger des images en haute résolution:"
|
||||
echo "1. Ouvrez le lien Google Maps dans un navigateur"
|
||||
echo "2. Faites un clic droit sur l'image > Inspecter l'élément"
|
||||
echo "3. Copiez l'URL de l'image (généralement sur googleusercontent.com)"
|
||||
echo "4. Modifiez les paramètres w=XXX par w=1600 et h=YYY par h=1200"
|
||||
echo "5. Ajoutez l'URL dans le tableau IMAGES du script"
|
||||
echo ""
|
||||
echo "Exemple d'URL Google Maps image:"
|
||||
echo "https://lh3.googleusercontent.com/gps-cs-s/...w=1600-h=1200-k-no"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Créer le dossier images s'il n'existe pas
|
||||
mkdir -p "$IMAGES_DIR"
|
||||
|
||||
# Télécharger chaque image
|
||||
success_count=0
|
||||
fail_count=0
|
||||
|
||||
for image_data in "${IMAGES[@]}"; do
|
||||
IFS='|' read -r url output_file place_name <<< "$image_data"
|
||||
|
||||
output_path="$IMAGES_DIR/$output_file"
|
||||
|
||||
if download_google_maps_image "$url" "$output_path" "$place_name"; then
|
||||
((success_count++))
|
||||
else
|
||||
((fail_count++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
echo "✅ Téléchargement terminé"
|
||||
echo "=========================================="
|
||||
echo "✅ Succès: $success_count"
|
||||
echo "❌ Échecs: $fail_count"
|
||||
echo ""
|
||||
|
||||