Interface : corrections à la souris et accès protégé
Onglet « Carte et corrections » : glisser une photo ou une vidéo, placer les médias sans position, masquer des zones de trace (pour un voyage, ou zone privée pour tous). Corrections notées dans voyage.toml ([positions], [corrections]) ou site.toml ([confidentialite]), appliquées par les pages ; les originaux ne changent pas. L'extraction garde les médias datés sans position pour qu'on puisse les placer. Accès : clé d'accès aléatoire à chaque lancement, session par cookie HttpOnly/SameSite=Strict, mot de passe facultatif (rtg mot-de-passe, empreinte scrypt hors du site, blocage après 5 échecs), accès réseau seulement avec mot de passe (--distant), HTTPS possible (--certificat, --cle), Content-Security-Policy et interdiction des cadres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
1 parent
06153a227e
commit
71779f6f53
17 files changed
+1047
-38
No files matched your search
@@ -0,0 +1,107 @@
|
||||
"""Corrections faites à la main (interface, onglet « Carte ») et appliquées à
|
||||
la génération des pages ; les fichiers d'origine ne sont jamais modifiés.
|
||||
|
||||
voyage.toml
|
||||
[positions] média déplacé ou placé à la main
|
||||
"IMG_4831.jpeg" = [48.85, 2.35]
|
||||
[corrections]
|
||||
masques = [[48.85, 2.35, 300]] zones (centre, rayon en m) retirées des traces
|
||||
|
||||
site.toml
|
||||
[confidentialite]
|
||||
zones = [[48.80, 2.10, 500]] zones retirées des traces de tous les voyages
|
||||
(autour de chez soi, par exemple)
|
||||
"""
|
||||
import csv
|
||||
import math
|
||||
import os
|
||||
|
||||
import gpxpy.gpx
|
||||
|
||||
|
||||
def masques(cfg):
|
||||
"""[(lat, lon, rayon_m)] : zones du voyage et zones privées du site."""
|
||||
res = []
|
||||
for liste in (cfg.get("corrections", {}).get("masques", []),
|
||||
cfg.get("confidentialite", {}).get("zones", [])):
|
||||
for m in liste:
|
||||
try:
|
||||
res.append((float(m[0]), float(m[1]), float(m[2])))
|
||||
except (TypeError, ValueError, IndexError):
|
||||
pass
|
||||
return res
|
||||
|
||||
|
||||
def positions(cfg):
|
||||
"""{nom de fichier: (lat, lon)} des médias placés à la main."""
|
||||
res = {}
|
||||
for nom, pos in cfg.get("positions", {}).items():
|
||||
try:
|
||||
res[nom] = (float(pos[0]), float(pos[1]))
|
||||
except (TypeError, ValueError, IndexError):
|
||||
pass
|
||||
return res
|
||||
|
||||
|
||||
def distance_m(lat1, lon1, lat2, lon2):
|
||||
x = math.radians(lon2 - lon1) * math.cos(math.radians((lat1 + lat2) / 2))
|
||||
y = math.radians(lat2 - lat1)
|
||||
return 6371000 * math.hypot(x, y)
|
||||
|
||||
|
||||
def dans_un_masque(lat, lon, zones):
|
||||
return any(distance_m(lat, lon, m[0], m[1]) <= m[2] for m in zones)
|
||||
|
||||
|
||||
def filtrer_piste(piste, zones):
|
||||
"""Retire d'une piste GPX les points situés dans les zones ; un segment
|
||||
traversé par une zone est coupé en deux. Renvoie le nombre de points retirés."""
|
||||
if not zones:
|
||||
return 0
|
||||
retires = 0
|
||||
nouveaux = []
|
||||
for seg in piste.segments:
|
||||
courant = gpxpy.gpx.GPXTrackSegment()
|
||||
for p in seg.points:
|
||||
if dans_un_masque(p.latitude, p.longitude, zones):
|
||||
retires += 1
|
||||
if courant.points:
|
||||
nouveaux.append(courant)
|
||||
courant = gpxpy.gpx.GPXTrackSegment()
|
||||
else:
|
||||
courant.points.append(p)
|
||||
if courant.points:
|
||||
nouveaux.append(courant)
|
||||
piste.segments = [s for s in nouveaux if len(s.points) > 1]
|
||||
return retires
|
||||
|
||||
|
||||
def lire_medias(voyage, cfg):
|
||||
"""Photos et vidéos des exports CSV, positions corrigées comprises.
|
||||
|
||||
Renvoie une liste de dicts {fichier, type, date, lat, lon, deplace} ; lat et
|
||||
lon valent None pour un média sans position."""
|
||||
corr = positions(cfg)
|
||||
res = []
|
||||
for nom_csv, type_ in (("export_photos.csv", "photo"), ("export_videos.csv", "video")):
|
||||
chemin = os.path.join(voyage, nom_csv)
|
||||
if not os.path.exists(chemin):
|
||||
continue
|
||||
vus = set()
|
||||
with open(chemin, encoding="utf-8", errors="replace") as f:
|
||||
for ligne in csv.reader(f):
|
||||
if len(ligne) < 4 or ligne[0] in vus:
|
||||
continue
|
||||
vus.add(ligne[0])
|
||||
try:
|
||||
lat, lon = float(ligne[2]), float(ligne[3])
|
||||
if lat == 0 and lon == 0:
|
||||
lat = lon = None
|
||||
except ValueError:
|
||||
lat = lon = None
|
||||
m = {"fichier": ligne[0], "type": type_, "date": ligne[1], "lat": lat, "lon": lon,
|
||||
"deplace": ligne[0] in corr}
|
||||
if m["deplace"]:
|
||||
m["lat"], m["lon"] = corr[ligne[0]]
|
||||
res.append(m)
|
||||
return res
|
||||
Reference in new issue
Block a user