﻿#!/bin/bash
# guiguishow.info - license : GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html)

# git and jq packages are required!


# User editable variables
gitRep=https://forge.afriendly.space/glucas/submarinecablemap.git

outputDir=$(dirname `readlink -f $0`)
cablesFile="${outputDir}/cables.json"
landingsFile="${outputDir}/landings.json"
summaryFile="${outputDir}/summary.txt"

summaryHeader="[[https://www.submarinecablemap.com/|Original work]] (on Google Maps).
[[https://www2.telegeography.com/submarine-cable-faqs-frequently-asked-questions|Original Frequently Asked Questions]].
[[https://github.com/telegeography/www.submarinecablemap.com/|Original data source (on GitHub)]].
[[https://forge.afriendly.space/glucas/submarinecablemap|Data source (on a self-hosted git)]].
[[http://shaarli.guiguishow.info/?UJyNPw|Steps to make your own copy of this map]].

**Last map update**: $(date '+%F').
**Last data source update**: freshnessReplacePattern.

**Help**:
 * If this panel closes or if you want to view another cable, click on \"À propos\" (bottom right, in small letters).
 * To search for a cable name, instead of ctrl+f, use \"/\" on Firefox, F3 on Chrome, etc.

**Cables** (cablesCountReplacePattern):"

# Inutile. Ces options peuvent aussi être configurées sur le calque et être héritées
# popupShape = Panel  => afficher description dans panneau latéral quand clic sur câble ou icône
# iconClass  = Circle => les icônes sont des cercles (plus petits que réglage par défaut)
#umapCablesParam='{ "_umap_options": {
#  "popupShape": "Panel",
#  "opacity": "1"
#  }
#}'
umapCablesParam='{ }'
#umapLandingsParam='{ "_umap_options": {
#  "iconClass": "Circle",
#  "popupShape": "Panel"
#  }
#}'
umapLandingsParam='{ }'
# End of user editable variables


# Internal / temporary variables
tmpDir=$(mktemp -d)

# 2021-08-15 : passage à la version 3 de l'API
apiDir="${tmpDir}/web/public/api/v3"

cablesDir="${apiDir}/cable/"
cablesListTmp="${tmpDir}/cablesList.tmp"
cablesFileWithCoord="${tmpDir}/cablesWithCoord.tmp"

landingsDir="${apiDir}/landing-point/"
landingsListTmp="${tmpDir}/landingsList.tmp"

dataFreshnessFile="${apiDir}/config.json"

cablesDefsAggFile="${tmpDir}/cablesDefsAgg.json"
landingsDefsAggFile="${tmpDir}/landingsDefsAgg.json"
# End of internal variables


# Fallback function
final_clean () {
  echo -e "\nRemoving temporary workdir ${tmpDir}…"
  rm -rf --preserve-root "$tmpDir"
}

set -e
trap ">&2 echo -e ' ERROR! UNEXPECTED END!' && final_clean ; exit 1" ERR


# main()
# command -v and type fail if alias
if ! hash git jq 2>/dev/null
then
  >&2 echo 'ERROR: git or jq command not found!'
  false # trap ;)
fi


# On récupère la matière première
echo -n 'Retrieving data from remote git repo…'
git clone -q "$gitRep" "$tmpDir"


# Nous avons besoin de ces fichiers mais ils ne doivent pas rester dans ce dossier sinon duplicatas quand on les agrège
mv "${cablesDir}/cable-geo.json" "$cablesListTmp"
rm "${cablesDir}/all.json"

mv "${landingsDir}/landing-point-geo.json" "$landingsListTmp"
# 2021-08-15 : ce fichier n'existe plus dans le dépôt git
# rm "${landingsDir}/all.json"


# On traite les câbles
echo -ne '\n\nWorking on cables…'

# On agrège, dans un même fichier, les caractéristiques (nom, longueur, etc.) de chaque câble
jq '.' "$cablesDir"/*.json > "$cablesDefsAggFile"

# Parce que ce caractère est UTF-8 et que les fonctions sub()/gsub de jq < 1.6 n'aiment pas ça
# (cela coupe des mots quand on essaye de remplacer « , » par « \n » dans les proprios de câbles).
# Voir http://shaarli.guiguishow.info/?GQNU7A
sed -i "s/’/'/" "$cablesDefsAggFile"

# On génère un fichier JSON qui nous convient. Pour chaque câble :
#  * On ajoute son nom + caractéristiques (longueur, proprio, etc.) + stations d'atterrissement + formatage umap (markdown) ;
#  * On ajoute des propriétés compréhensibles par umap : couleur du câble + ajouts utilisateur (voir $umapCablesParam) ;
#  * On supprime les informations inutilisables par umap : ancien attribut stockant la couleur, id.
# 2021-08-15 : Telegeography a poussé la version 3 de l'API. Changements :
#  * La couleur du câble est déjà préfixée par « # », nous n'avons plus à le faire ;
#  * Une information supplémentaire, le fournisseur du câble est communiquée, nous l'affichons ;
#  * Le lien entre un câble et sa définition ne se fait plus avec, d'un côté, l'attribut « slug » et de l'autre l'attribut « id »
#    car l'id est désormais présent des deux côtés et le slug a été supprimé ;
#  * Factorisation des instructions jq qui récupère les caractéristiques / la définition d'un câble ;
#  * Correction de la mise en forme des propriétaires d'un câble : il manquait une espace après « * » ;
#  * Utilisation de l'opérateur d'alternative (« // ») pour afficher « n/a » quand une info n'est pas disponible ;
# 2021-08-20 : Les coordonnées géographiques des stations d'atterrissement ne sont plus communiquées dans la définition des câbles,
#  donc on fusionne un troisième fichier, la liste des stations d'atterrissement, afin d'itérer dessus et
#  de continuer ainsi à afficher un lien cliquable vers les stations dans la description d'un câble.
#  Les coordonnées sont inversées par rapport à ce qu'umap/OSM attend dans une URL, d'où on ne peut pas utiliser map() comme avant…
jq --slurpfile cablesDefs "$cablesDefsAggFile" --slurpfile landingsList "$landingsListTmp" --argjson umapCablesParam "$umapCablesParam" '
  . as $racine | .featuresDup |= $racine.features | del(.features) | .features +=
    [
      .featuresDup[] | . as $cable
        | $cablesDefs[] | select(.id==$cable.properties.id)
          | .name as $cableName
          | "\(.rfs // "n/a")" as $cablerfs
          | "\(.length // "n/a")" as $cablelength
          | "\(.owners // "n/a" | gsub(", ";"\n * "))" as $cableowners
          | "\(.suppliers // "n/a" | gsub(", ";"\n * "))" as $cablesuppliers
          | "\(.url // "n/a")" as $cableurl
          | "\(.notes // "n/a")" as $cablenotes
          | [
              .landing_points[]
                | .id as $tmpCableId
              | $landingsList[] | .features[]
                | select (.properties.id==$tmpCableId) | " * [[#12/\(.geometry.coordinates[1])/\(.geometry.coordinates[0])|\(.properties.name)]]"
            ] as $cableLandings
        | $cable
          | .properties += { "name": $cableName}
          | .properties += { "description": "**Ready For Service**: \($cablerfs)\n**Cable Length**: \($cablelength)\n**Owners**:\n * \($cableowners)\n**Suppliers**:\n * \($cablesuppliers)\n**URL**: \($cableurl)\n**Notes**: \($cablenotes)\n\n**Landing Points**:\n\($cableLandings | join("\n"))" }
          | .properties += $umapCablesParam
          | .properties._umap_options += { "color": $cable.properties.color }
          | del(.properties.color) | del(.properties.id) | del(.properties.feature_id)
    ]
  | del(.featuresDup)' "$cablesListTmp" > "$cablesFileWithCoord"

# L'attribut « coordinates » dans les propriétés d'un câble indique les coordonnées du "milieu" d'un câble.
# Nous les utilisons désormais pour afficher un lien cliquable vers chaque câble dans la description de la carte.
# Mais on ne veut pas que cet attribut demeure dans le fichier final afin de ne pas risquer d'embrouiller uMap (prudence excessive).
jq 'del(.features[].properties.coordinates)' "$cablesFileWithCoord" > "$cablesFile"


# On traite les stations d'atterrissement
echo -ne '\n\nWorking on landings…'

# Agrégation
jq '.' "$landingsDir"/*.json > "$landingsDefsAggFile"

# On génère un fichier JSON qui nous convient. Pour chaque station :
#  * On ajoute la liste des câbles qui passent par cette station en description + formatage umap (md) ;
#  * On ajoute des propriétés compréhensibles par umap : voir $umapLandingsParam ;
#  * On supprime les informations inutilisables par umap : id.
# 2021-08-15 : Telegeography a poussé la version 3 de l'API. Changements :
#  * Le lien entre une station et sa définition ne se fait plus avec, d'un côté, l'attribut « slug » et de l'autre l'attribut « id »
#    car l'id est désormais présent des deux côtés et le slug a été supprimé ;
# 2021-08-20 : la liste des câbles contient les coordonnées géographiques du "milieu" d'un câble.
#  Nous nous en servons afin d'afficher, dans la description d'une station, un lien vers chaque câble géographique.
#  Les coordonnées sont inversées par rapport à ce qu'umap/OSM attend dans une URL, d'où nous accédons à chaque case du tableau plutôt que d'utiliser map().
jq --slurpfile landingsDefs "$landingsDefsAggFile" --slurpfile cablesList "$cablesListTmp" --argjson umapLandingsParam "$umapLandingsParam" '
  . as $racine | .featuresDup |= $racine.features | del(.features) | .features +=
    [
      .featuresDup[] | . as $landing
        | $landingsDefs[] | select(.id==$landing.properties.id)
          | [
              .cables[]
                | .id as $tmpCableId
              | $cablesList[] | .features[]
                | select (.properties.id==$tmpCableId) | " * [[#12/\(.properties.coordinates[1])/\(.properties.coordinates[0])|\(.properties.name)]]"
            ] as $cablesName
        | $landing
          | .properties += { "description": "**Cables:**\n\($cablesName | join("\n"))"}
          | .properties += $umapLandingsParam
          | del(.properties.id)
    ]
  | del(.featuresDup)' "$landingsListTmp" > "$landingsFile"


# On récupère la liste des noms de tous les câbles afin de les ajouter dans la description de la carte
echo -e '\n\nRetrieving cables name list…'

# 2021-08-15 : on récupère la date de mise à jour des données sources dans le dépôt git et
#  on l'affiche dans la description de la carte. Le bashisme permet d'afficher uniquement la date, pas l'heure.
dataFreshness=$(jq -r '.creation_time' "$dataFreshnessFile")
sed "s/freshnessReplacePattern/${dataFreshness%T*}/" <<< "$summaryHeader" > "$summaryFile"

# jq -r '" * \(.features[].properties.name)"' "$cablesFile" >> "$summaryFile"
# 2021-08-15 : la liste des câbles n'est plus triée par ordre alphabétique, c'est à nous de le faire.
#  * D'après son manuel, jq trie les string « in alphabetical order (by unicode codepoint value) »
#    donc si la première lettre d'un câble est en minuscule, le câble termine en fin de liste.
#    Utiliser ascii_downcase ou ascii_upcase avant de trier fonctionne, mais certains noms de câble sont des
#    acronymes que nous voulons conserver.
#    Écrire une fonction jq ne nous tente pas. D'où l'utilisation de sort.
#  * Pourquoi uniq ? Car, dans les fichiers sources, plusieurs segments de câble portent le même nom et le même
#    id (mais pas le même feature_id). Il s'agit de câbles qui forment une boucle ou qui ont > 2 embranchements.
#    La carte originale affiche les différents segments, donc c'est légitime donc on ne peut pas les filtrer.
#    La carte originale affiche les mêmes infos dans son panneau latéral, quel que soit le segment d'un même câble
#    sur lequel on clique. Notre carte réagit de la même façon.
#    En revanche, la carte originale n'affiche pas le nom de câble doublonnés dans la description de la carte.
#    Nous voulons obtenir le même résultat, d'où l'usage de uniq.
# jq -r '" * \(.features[].properties.name)"' "$cablesFile" | sort | uniq >> "$summaryFile"
# 2021-08-20 : on affiche un lien vers chaque câble en utilisant les coordonées géographiques
#  qui pointent vers le "milieu" d'un câble.
#  Les coordonnées sont inversées par rapport à ce qu'umap/OSM attend dans une URL,
#  d'où nous accédons à chaque case du tableau plutôt que d'utiliser map().
#  On laisse une espace avant d'afficher le nom du câble afin de pouvoir indiquer à sort et uniq de comparer à partir de là
#  (et donc de trier par nom du câble).
cableSummary=$(jq -r '.features[] | " *[[#12/\(.properties.coordinates[1])/\(.properties.coordinates[0])| \(.properties.name)]]"' "$cablesFileWithCoord" \
  | sort -k 2 | uniq -f 1)

# 2021-12-26 : on affiche le nombre de câbles dans la description de la carte
cableCount=$(wc -l <<< "$cableSummary")
sed -i "s/cablesCountReplacePattern/${cableCount}/" "$summaryFile"

# On met la liste des câbles obtenue précédemment dans la description
echo "$cableSummary" >> "$summaryFile"


final_clean

echo -e "\nEnd. Files to use with OSM: ${cablesFile} and ${landingsFile} . Cables list for map summary: ${summaryFile} ."
exit 0
