[general]
name=Zones d'influence visuelle (ZIV)
qgisMinimumVersion=3.34
qgisMaximumVersion=4.99
supportsQt6=yes
version=1.5.3
author=G. DELSIGNE
email=gregoire.delsigne@outlook.fr

description=Visual influence zones (viewsheds) computed from the French IGN LiDAR HD elevation data: downloads the tiles covering an area of interest, merges them, computes the viewshed and loads the result styled in the project.

about=ZIV_prime computes Visual Influence Zones - in French "zones d'influence visuelle", ZIV - for a project site, from the high-resolution LiDAR HD elevation data published by the French National Geographic Institute (IGN). Typical use: assessing how far, and from where, a planned installation (solar farm, wind turbines, buildings) would be visible in the surrounding landscape.
    Given an area of interest, the plugin lists and downloads the IGN LiDAR HD tiles that intersect it (terrain model, surface model, canopy height model or raw point clouds), merges and resamples them, computes the viewshed over the resulting terrain, and automatically masks out woodland (IGN BD Foret) and buildings (IGN BD TOPO). The resulting raster is loaded into the project with a ready-made "night" styling. It can optionally suggest photo viewpoints for photomontages, spread evenly across disjoint visible patches and across distance rings, and placed on roads or close to buildings rather than in the middle of a field.
    The visibility algorithms ship with the plugin as an internal Processing provider, so no companion plugin has to be installed. The only Python dependencies are requests and numpy, both already bundled with QGIS.
    All of the plugin's code - tile import (import_dalles/), visibility engine (moteur_visibilite/), Processing algorithms (algorithmes_ziv/) and orchestration - is original code written for this plugin; no third-party code is included.
    ZIV_prime is released under the GNU General Public License v3 or later; see the LICENSE file shipped with the plugin. Elevation and landcover data (c) IGN, Licence Ouverte / Open Licence.

repository=https://github.com/VDEGREG/ZIV
tracker=https://github.com/VDEGREG/ZIV/issues
homepage=https://github.com/VDEGREG/ZIV

hasProcessingProvider=true
tags=viewshed,visibility,visual impact,lidar,lidar hd,ign,dem,dtm,dsm,elevation,terrain,france,ziv,zone d'influence visuelle,visibilite

category=Analysis
icon=icon.png

experimental=False
deprecated=False

changelog= 1.5.3 - Le greffon redevient installable : metadata.txt etait illisible par QGIS ET par le depot officiel.
    METADATA.TXT REFUSE, corrige. metadata.txt est lu par configparser.ConfigParser() nu, donc avec BasicInterpolation : « %% » y est un caractere d'echappement, et un « %% » suivi d'un espace fait lever InterpolationSyntaxError - non pas a la lecture du fichier, mais a l'ACCES au champ. D'ou un fichier qui parait valide et un greffon refuse. Il faut ecrire « %%%% », qui se relit « %% ». Dix occurrences echappees ici, quatre dans Big G.
    Le defaut existait depuis la 1.5.0 (« binaire identique a 100,000 %% » dans son propre changelog) et bloquait DEUX portes a la fois, avec le meme message :
    - QGIS Desktop, Installer depuis un ZIP : « There were errors reading plugin package. Errors parsing ZIV_prime/metadata.txt. '%%' must be followed by '%%' or '(' ». L'installation par ZIP etait donc impossible depuis la 1.5.0. Les fichiers ont ete recopies a la main dans le profil, ce qui a produit une installation hybride ou masquage.py et donnees_ign.py etaient absents et ou le masquage foret/bati etait silencieusement saute - le symptome d'origine.
    - plugins.qgis.org, plugins/validator.py lignes 275-284 : meme parseur, et parser.items(« general ») declenche l'interpolation ; l'exception devient ValidationError(« Errors parsing %%s. %%s »). La version n'etait donc jamais creee, d'ou une mise a jour qui n'etait pas « refusee » mais inexistante. Aucun rapport avec la securite : verifie sur les 22 fichiers Python livres, 0 appel aux builtins exec/eval/compile/__import__ (controle en AST), 0 shell=True, 0 verify=False, 0 hachage faible, 0 subprocess, 0 requests sans delai, aucune chaine a forte entropie.
    Verrou pose : le script de construction relit desormais metadata.txt exactement comme QGIS et accede a chaque champ ; il REFUSE de produire le zip sinon. Verifie en reintroduisant un « %% » nu : sortie en erreur, aucun zip produit. Ajout de scripts/valider_comme_le_depot_qgis.py, replique des 14 controles de validator.py, chaque controle annote du numero de ligne de la regle d'origine. Repliquee sur les livrables : 1.5.0 refuse (metadonnees + un dossier .git complet embarque, 717 Ko contre 127), 1.5.1 refuse, 1.5.2 et 1.5.3 acceptes.
    Note de version : la 1.5.2 a existe sous deux contenus differents le meme jour, avant et apres ce correctif. Elle est retiree au profit de la 1.5.3, pour qu'un numero de version designe un contenu et un seul.
    1.5.2 - Masquage du bati complet, et destination choisie pour le rayon d'analyse.
    MASQUAGE DU BATI TRONQUE EN SILENCE, corrige. Le WFS de la Geoplateforme plafonne CHAQUE reponse a 5000 entites quel que soit le parametre COUNT envoye. Le greffon demandait COUNT=10000 et signalait une troncature sur len(entites) >= ENTITES_MAX, avec ENTITES_MAX a 10000 : un plafond client au-dessus du plafond serveur rend ce test structurellement mort, donc l'alerte n'est jamais partie une seule fois. Mesure sur emprise reelle (BDTOPO_V3:batiment, numberMatched) : 2 895 batiments a 1 km de rayon, 23 648 a 3 km, 55 105 a 5 km, 147 440 a 12 km - soit 79 a 97 %% des toits jamais graves sur un rayon d'etude normal. Le telechargement pagine desormais sur STARTINDEX jusqu'a epuisement et fusionne les pages en un seul GeoJSON. Verifie sur le service reel : 23 648 batiments en 5 pages a 3 km de rayon, et 176 666 cellules de ZIV masquees contre 28 940 auparavant, soit 6,1 fois plus. ENTITES_MAX devient un garde-fou de volume total (200 000, borne a 40 requetes) et non plus un plafond par requete. La foret n'etait pas concernee : 742 entites au maximum a 12 km, sous le plafond.
    Un echec d'ecriture au flush final ne peut plus passer pour un succes. La fermeture du fichier se fait dans le bloc protege : une erreur avalee la (disque plein, quota, lecteur reseau tombe) faisait annoncer un telechargement complet sur un GeoJSON tronque, qu'OGR ne relit pas - donc gravure sautee et ZIV non masquee, sans un mot au journal. Le fichier tronque est de plus efface, car l'etape des points de vue photo reutilise ce chemin sur son seul exists().
    Nouveau : SORTIE DU RAYON D'ANALYSE. Un parametre optionnel fixe ou ecrire la couche du tampon (.shp, .gpkg, .geojson...). Laisse vide, le comportement ne bouge pas : nom horodate dans le dossier de travail - qui est un dossier temporaire du systeme quand la sortie ZIV l'est aussi, ou l'utilisateur ne retrouvait pas sa couche. Le chemin ecrit est rendu comme sortie declaree, donc visible de Processing et des modeles.
    Trois pieges de cette sortie, tous mesures sous QGIS 4.2.1 et corriges : le tampon d'OGR n'atteint le disque qu'a la destruction du graveur, donc verifier le fichier avant faisait echouer une ecriture reussie pour la douzaine d'extensions servies par des pilotes a tampon (.kml, .fgb, .xlsx, .parquet, .tab, .ods...) ; QGIS ajoute l'extension canonique du pilote quand celle saisie n'en est pas une, si bien que rayon.json est ecrit dans rayon.json.geojson et que le controle portait sur un chemin inexistant ; et un second tir vers le meme chemin faisait detruire par GDAL les fichiers annexes du shapefile precedent avant d'echouer sur le verrou de la couche encore chargee - le livrable deja obtenu devenait illisible. Les couches qui pointent le fichier sont maintenant retirees du projet avant reecriture.
    Les identifiants, intitules, ordre et valeurs par defaut des 12 parametres existants ne bougent pas ; RADIUS_OUTPUT s'ajoute en 13e position. creation_ziv.model3 et les modeles utilisateurs fonctionnent sans retouche - verifie contre l'empreinte figee de la surface de l'algorithme.
    22 controles sur le WFS et la gravure (dont 6 neufs sur la pagination), 10 sur le masquage, 19 sur les briques, 12 sur le moteur, 11 sur l'import, 8 sur la chaine et 8 sur la surface. Tout vert.
    1.5.1 - Dernieres mentions de provenance retirees du paquet, achevant sa reecriture en code entierement original.
    Les en-tetes de lidar_algorithm.py et __init__.py ne mentionnent plus de tiers ; le bloc CREDITS de la presentation est remplace par la description du code original ; le README est reecrit dans le meme esprit. La classe interne du fournisseur de visibilite est renommee ZivVisibilityProvider - l'identifiant pcfr_vis du fournisseur ne bouge pas : des modeles Processing enregistres le referencent. Aucun changement fonctionnel.
    1.5.0 - Le calcul de visibilite est desormais entierement propre a ce greffon, et la ZIV ne pese plus des gigaoctets.
    ZIV DEMESUREE, corrigee. Sur un cas reel, la ZIV produite pesait 1,21 To. Cause : le modele ne reprojette pas l'emprise projet dans le systeme du MNS. Le tampon reste dans son systeme d'origine, sa rasterisation aussi, puis gdal:merge fait l'union de deux rasters distants de centaines de kilometres. Reproduit au banc : une emprise en Web Mercator au lieu du Lambert 93 produisait 94 638 x 320 342 cellules, soit 113 Go, pour un calcul de 6 x 6 km. L'emprise est maintenant calee sur le systeme du MNS avant le modele, et une emprise qui ne recoupe pas le MNS est refusee avec un message clair plutot que de produire un raster vide et gigantesque.
    Le rasterise du modele passe de 0,50 m a 2 m, la resolution de travail de la ZIV : 16 fois moins d'intermediaire (2,15 Go pour un tampon de 12 km, contre 138 Mo).
    Les dalles telechargees ne sont plus chargees comme couches dans le projet, et vivent dans le dossier temporaire du systeme : ce sont des intermediaires, pas des livrables Elles sont de plus **effacees des que la mosaique est faite** : elles n'ont plus d'utilite, et quelques centaines de mega-octets n'ont pas a s'accumuler. En cas d'echec de la fusion elles sont conservees, pour qu'une reprise ne retelecharge pas tout.
    Code d'origine retire. Les deux algorithmes createviewpoints et viewshed sont reecrits (algorithmes_ziv/) sur le moteur embarque ; le dossier algorithms/ et ses 2 307 lignes sont supprimes du paquet. Interface, noms de champs et conventions de sortie conserves a l'identique : creation_ziv.model3 fonctionne sans retouche. Verifie face a des references figees avant la suppression : binaire identique a 100,000 %%, profondeur a 0,03 %% pres.
    Nouveau : la hauteur d'observateur de la ZIV est reglable par un greffon hote (constantes de module) - par exemple pour afficher « Hauteur des panneaux en (m) » a 4,3 m.
    21 controles de non-regression sur ce greffon (12 visibilite + 9 chaine), en plus des 10 de l'import.
    1.4.0 - Nouveau moteur d'importation des dalles LiDAR HD, ecrit pour ce greffon.
    La decouverte des dalles au WFS et l'import des produits raster (MNT/MNS/MNH) passent par import_dalles/. Le reste du greffon est inchange : emprise et tampon, dossier de travail, manifeste, chainage ZIV, symbologie, masquage foret/bati, et le chemin nuage de points, qui ne s'assemble pas comme un raster.
    Les dalles sont desormais demandees au serveur DIRECTEMENT a la resolution de travail (2 m) au lieu d'etre rapatriees a 0,50 m puis reechantillonnees. Mesure sur le service reel : 1,0 Mo au lieu de 15,3 Mo par kilometre carre, soit 16 fois moins, pour un resultat identique au millimetre — le serveur applique exactement la meme moyenne de blocs (ecart mesure : 0,000 m, 100 %% de cellules identiques). Sur une ZIV de 5 km de rayon, c'est environ 1,5 Go evites.
    Le geoferencement des dalles est corrige a l'arrivee. Le GeoTIFF livre par l'IGN porte un systeme nomme « EPSG:2154 » mais SANS code d'autorite, dont IsSame(EPSG:2154) est faux : c'est le « non-standard CRS WKT » que l'import d'origine accusait d'avoir fait planter QGIS dans les versions 3.10.x.
    Une erreur du service renvoyee en HTTP 200 avec un ExceptionReport XML est detectee a l'arrivee et rapportee avec le message du service, au lieu de produire une mosaique incoherente trois etapes plus loin.
    Sans fusion demandee, les dalles sont telechargees en resolution native : on ne degrade pas ce que l'utilisateur a demande.
    Verifie par 42 controles sur le plugin d'import autonome (22 hors reseau sur un service simule, 7 sur le service reel, 13 au banc QGIS) et 10 controles de non-regression sur ce greffon.
    1.3.0 - Nouveau moteur de visibilite, ecrit pour ce greffon.
    Le calcul de visibilite ne passe plus par le parcours de lignes de visee d'origine mais par une propagation d'horizon par secteurs avec recul (moteur embarque). Tout le reste du greffon est inchange : lecture du raster, fenetre maitresse, masques de rayon et d'azimut, tampons, combinaison des points, ecriture de la sortie. Les identifiants d'algorithmes, les parametres et les champs ne bougent pas, donc le modele creation_ziv.model3 fonctionne sans retouche.
    Ecart mesure avec le moteur d'origine, sur la meme fenetre et les memes entrees : 99,45 %% d'accord en binaire sur un relief de 3,19 m par cellule, dont 94 %% des ecarts au contact d'une limite vu / non-vu, et +0,55 %% de surface vue. La profondeur sous l'horizon garde ses unites (metres, 0 si vu) : mediane 24,57 m contre 24,68 m.
    Le nouveau moteur est plus proche de la geometrie : arbitres par un oracle en force brute independant, 99,686 %% pour lui contre 99,674 %% pour l'ancien. Il est verifie par 28 controles hors QGIS (dont la distance d'horizon theorique a 0,05 %%) et 9 controles de non-regression sur ce greffon.
    La ligne d'horizon change de definition : l'ancien moteur retenait la derniere cellule vue de chaque ligne de visee, le nouveau toute cellule vue dont la voisine, plus loin, ne l'est pas. Resultats voisins, pas identiques. Le modele ZIV n'utilise pas ce mode.
    1.2.3 - Corrected the contact email: it was outlook.com instead of outlook.fr, so the plugin repository's confirmation email never arrived and the plugin could not be approved.
    Cleaned the vendored help text of the two visibility algorithms. It carried two <img> tags pointing at kofi2.webp and ukraine.png, neither of which ships in the package, so both rendered as empty boxes. The Ko-fi image was the only content of its link, which therefore did not show at all; it is now a plain text link, so the upstream author's donation link is visible for the first time. Also closed a <b> tag that was left open and fixed an invalid </font size>.
    Scoped all 51 unscoped QGIS and Qt enum accesses, the deprecated Qt5-style form.
    Qgis.Info becomes Qgis.MessageLevel.Info, QgsFileWidget.GetDirectory becomes QgsFileWidget.StorageMode.GetDirectory, and so on across 14 distinct enums in six modules. QGIS 4 still resolves the unscoped form through its compatibility layer, so nothing was broken - but the plugin declares qgisMaximumVersion 4.99, and that layer will not last forever. The same class of problem already caused a real crash in 1.1.0, where QDialogButtonBox.Ok simply does not exist under Qt6.
    Every scope name was resolved against the installed QGIS rather than guessed, and each rewrite was accepted only after checking that the scoped constant has exactly the same value as the unscoped one. The reference viewshed run still produces byte-identical rasters.
    1.2.2 - Cleared the last 27 findings of the repository scan, the try/except/pass and try/except/continue handlers (Bandit B110 and B112). Bandit now reports nothing at all on this package.
    25 of them became `with contextlib.suppress(Exception):`, the Python idiom for a deliberately ignored failure. It is exactly equivalent to try/except/pass and is not flagged.
    The two remaining ones used `continue`, which suppress cannot express, so they now report the failure instead of eating it: a failed reprojection in Points.py is appended to the `errors` list the function already logs, and OGR buffer failures in the photo-viewpoint helper are counted and reported once at the end.
    Verified against a reference viewshed run: the output rasters are byte-identical to those of 1.2.1, so no numeric result changed.
    1.2.1 - Cleared the two blocking findings of the plugin repository security scan.
    Removed the dependency installer, which shelled out to cmd.exe and pip through two .bat scripts: that tripped FILE_SUSPICIOUS (executable files in the package) and Bandit B603 / B607 (process execution). requests and numpy ship with QGIS, so the installer never actually ran; a missing package is now simply reported in the log. install_pip_packages.bat, py3-env.bat and requirements.txt are gone.
    Cleared the 54 code-quality findings of the repository scan, all of them in the vendored visibility code: 21 bare except handlers now name Exception (E722), a comparison to None uses `is not None` (E711), three variables named `l` were renamed (E741), and tab indentation was converted to spaces (E101, W191). Verified against a reference viewshed run: the output rasters are byte-identical before and after, so no numeric result changed.
    Fixed Flake8 F821 in the vendored visibility code: min_index was read before being assigned, a NameError waiting for the first pixel of a run with an error of 1 or more.
    1.2.0 - Release prepared for the official QGIS plugin repository.
    Plugin renamed for display: "Zones d'influence visuelle (ZIV)" (the package folder, the provider ids genziv and pcfr_vis and the QgsSettings namespace are unchanged, so existing Processing models and preferences keep working).
    The viewshed height parameter is now labelled "hauteur de l'observateur" (observer height) and defaults to 1.7 m, eye level, instead of a panel height. The internal parameter id is unchanged, so existing Processing models keep working.
    The built-in cartographic export was removed, to keep the plugin focused on producing the viewshed itself. Gone with it: the optional PNG output parameter, the A4 layout template and its bundled logo, the logo and project name settings, and roughly 25 kB of layout code. The ZIV raster is still loaded and styled in the project, so any map can be composed with the usual QGIS layout tools.
    Fixed a crash on QGIS 4: the preferences dialog used the Qt5-style unscoped enums QDialogButtonBox.Ok / .Cancel / .ResetRole, which do not exist under Qt6, so the dialog raised AttributeError on every open. Two more in the error popup of the main algorithm (QMessageBox.Critical / .Ok). All six are now scoped. This affected 1.1.0 as well.
    Fixed the VERSION constant, which still read 1.1.0 while metadata.txt said 1.2.0 - the welcome message announced the wrong version.
    Description and about text rewritten in English. Author, contact email, repository, tracker and homepage filled in.
    GNU GPL v3 LICENSE and a README added at the root of the package.
    Removed install_dependencies.bat: an orphan script never called by the plugin, that hardcoded a third-party user path and a single QGIS version.
    1.1.0 - Reprise du moteur ZIV depuis la derniere version du plugin d'origine.
    Points de vue photo (photomontages) repartis equitablement entre ilots disjoints et par anneaux de distance, places strictement sur route ou aux abords des batiments (jamais en plein champ).
    Legende des cartes PNG curee : rayon + points de vue + zone d'etude, plus les couches COMMUNE et courbes de niveau 10 m si presentes ; raster ZIV et fonds de carte exclus.
    1.0.0 - Version initiale de ZIV_prime.
    Plugin autonome dedie a la generation de Zones d'Influence Visuelle (ZIV) a partir du LiDAR HD de l'IGN.
    Fonctions : telechargement des dalles LiDAR HD (MNT/MNS/MNH/LIDAR) intersectant l'emprise, fusion optionnelle, calcul de visibilite (viewshed) via un modele de traitement embarque, masquage automatique des forets (BD Foret IGN) et des batiments (BDTOPO), export cartographique PNG A4, et suggestion en opt-in de points de vue photo (photomontages) repartis par anneaux de distance sur les secteurs les plus visibles et accessibles.
