up wiki compare
This commit is contained in:
parent
ce508974c9
commit
2f49ef6479
23 changed files with 567403 additions and 5132 deletions
|
@ -9,6 +9,175 @@ use Symfony\Component\HttpFoundation\Request;
|
|||
|
||||
class WikiController extends AbstractController
|
||||
{
|
||||
#[Route('/admin/wiki/missing-translations', name: 'app_admin_wiki_missing_translations')]
|
||||
public function missingTranslations(): Response
|
||||
{
|
||||
$csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv';
|
||||
|
||||
if (!file_exists($csvFile)) {
|
||||
$this->addFlash('error', 'Le fichier wiki_pages.csv n\'existe pas.');
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
|
||||
$csvData = array_map('str_getcsv', file($csvFile));
|
||||
$headers = array_shift($csvData);
|
||||
|
||||
$wikiPages = [];
|
||||
|
||||
// Process CSV data
|
||||
foreach ($csvData as $row) {
|
||||
$page = array_combine($headers, $row);
|
||||
$wikiPages[$page['key']][$page['language']] = $page;
|
||||
}
|
||||
|
||||
// Find French pages without English translations
|
||||
$frenchOnlyPages = [];
|
||||
foreach ($wikiPages as $key => $languages) {
|
||||
if (isset($languages['fr']) && !isset($languages['en'])) {
|
||||
$frenchOnlyPages[$key] = $languages['fr'];
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by key
|
||||
ksort($frenchOnlyPages);
|
||||
|
||||
return $this->render('admin/wiki_missing_translations.html.twig', [
|
||||
'french_only_pages' => $frenchOnlyPages
|
||||
]);
|
||||
}
|
||||
#[Route('/admin/wiki/suspicious-deletions', name: 'app_admin_wiki_suspicious_deletions')]
|
||||
public function suspiciousDeletions(): Response
|
||||
{
|
||||
$csvFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/wiki_pages.csv';
|
||||
$jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json';
|
||||
|
||||
if (!file_exists($csvFile) || !file_exists($jsonFile)) {
|
||||
$this->addFlash('error', 'Les fichiers nécessaires n\'existent pas.');
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
|
||||
// Load CSV data
|
||||
$csvData = array_map('str_getcsv', file($csvFile));
|
||||
$headers = array_shift($csvData);
|
||||
|
||||
// Process CSV data to find French pages
|
||||
$frenchPages = [];
|
||||
foreach ($csvData as $row) {
|
||||
$page = array_combine($headers, $row);
|
||||
if ($page['language'] === 'fr') {
|
||||
$frenchPages[$page['key']] = $page;
|
||||
}
|
||||
}
|
||||
|
||||
// Load JSON data
|
||||
$jsonData = json_decode(file_get_contents($jsonFile), true);
|
||||
|
||||
// Find pages with suspicious deletions
|
||||
$suspiciousPages = [];
|
||||
foreach ($jsonData as $page) {
|
||||
if (isset($page['fr_page']) && isset($page['en_page'])) {
|
||||
// Calculate deletion percentage
|
||||
$enWordCount = (int)$page['en_page']['word_count'];
|
||||
$frWordCount = (int)$page['fr_page']['word_count'];
|
||||
$wordDiff = $enWordCount - $frWordCount;
|
||||
|
||||
// If English has more words and the difference is significant (>30%)
|
||||
if ($wordDiff > 0 && $frWordCount > 0 && ($wordDiff / $enWordCount) > 0.3) {
|
||||
$page['deletion_percentage'] = round(($wordDiff / $enWordCount) * 100, 2);
|
||||
$suspiciousPages[] = $page;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by deletion percentage (highest first)
|
||||
usort($suspiciousPages, function($a, $b) {
|
||||
return $b['deletion_percentage'] <=> $a['deletion_percentage'];
|
||||
});
|
||||
|
||||
return $this->render('admin/wiki_suspicious_deletions.html.twig', [
|
||||
'suspicious_pages' => $suspiciousPages
|
||||
]);
|
||||
}
|
||||
#[Route('/admin/wiki/tag-proposals', name: 'app_admin_wiki_tag_proposals')]
|
||||
public function tagProposals(): Response
|
||||
{
|
||||
// URL of the OSM wiki page that lists tag proposals
|
||||
$url = 'https://wiki.openstreetmap.org/wiki/Proposed_features';
|
||||
|
||||
try {
|
||||
$html = file_get_contents($url);
|
||||
|
||||
if ($html === false) {
|
||||
throw new \Exception('Failed to fetch the tag proposals page');
|
||||
}
|
||||
|
||||
// Create a DOM parser
|
||||
$dom = new \DOMDocument();
|
||||
@$dom->loadHTML($html);
|
||||
$xpath = new \DOMXPath($dom);
|
||||
|
||||
// Find the table with proposals
|
||||
$tables = $xpath->query("//table[contains(@class, 'wikitable')]");
|
||||
$proposals = [];
|
||||
|
||||
if ($tables->length > 0) {
|
||||
// Get the first table which contains the active proposals
|
||||
$table = $tables->item(0);
|
||||
$rows = $xpath->query(".//tr", $table);
|
||||
|
||||
// Skip the header row
|
||||
for ($i = 1; $i < $rows->length; $i++) {
|
||||
$row = $rows->item($i);
|
||||
$cells = $xpath->query(".//td", $row);
|
||||
|
||||
if ($cells->length >= 4) {
|
||||
$proposal = [
|
||||
'feature' => $xpath->query(".//a", $cells->item(0))->item(0)->textContent,
|
||||
'url' => 'https://wiki.openstreetmap.org' . $xpath->query(".//a", $cells->item(0))->item(0)->getAttribute('href'),
|
||||
'description' => $cells->item(1)->textContent,
|
||||
'proposer' => $cells->item(2)->textContent,
|
||||
'status' => $cells->item(3)->textContent,
|
||||
];
|
||||
|
||||
$proposals[] = $proposal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('admin/wiki_tag_proposals.html.twig', [
|
||||
'proposals' => $proposals
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->addFlash('error', 'Erreur lors de la récupération des propositions de tags : ' . $e->getMessage());
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
}
|
||||
|
||||
#[Route('/admin/wiki/random-suggestion', name: 'app_admin_wiki_random_suggestion')]
|
||||
public function randomSuggestion(): Response
|
||||
{
|
||||
$jsonFile = $this->getParameter('kernel.project_dir') . '/wiki_compare/outdated_pages.json';
|
||||
|
||||
if (!file_exists($jsonFile)) {
|
||||
$this->addFlash('error', 'Le fichier outdated_pages.json n\'existe pas.');
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
|
||||
$jsonData = json_decode(file_get_contents($jsonFile), true);
|
||||
|
||||
if (empty($jsonData)) {
|
||||
$this->addFlash('error', 'Aucune page à améliorer n\'a été trouvée.');
|
||||
return $this->redirectToRoute('app_admin_wiki');
|
||||
}
|
||||
|
||||
// Select a random page from the outdated pages
|
||||
$randomPage = $jsonData[array_rand($jsonData)];
|
||||
|
||||
return $this->render('admin/wiki_random_suggestion.html.twig', [
|
||||
'page' => $randomPage
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/wiki', name: 'app_admin_wiki')]
|
||||
public function index(): Response
|
||||
|
|
37
templates/admin/_wiki_navigation.html.twig
Normal file
37
templates/admin/_wiki_navigation.html.twig
Normal file
|
@ -0,0 +1,37 @@
|
|||
<nav class="navbar navbar-expand-lg navbar-light bg-light mb-4">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{{ path('app_admin_wiki') }}">Wiki OSM</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#wikiNavbar" aria-controls="wikiNavbar" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="wikiNavbar">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki' %}active{% endif %}" href="{{ path('app_admin_wiki') }}">
|
||||
<i class="bi bi-list-ul"></i> Liste des pages
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_random_suggestion' %}active{% endif %}" href="{{ path('app_admin_wiki_random_suggestion') }}">
|
||||
<i class="bi bi-shuffle"></i> Suggestion aléatoire
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_tag_proposals' %}active{% endif %}" href="{{ path('app_admin_wiki_tag_proposals') }}">
|
||||
<i class="bi bi-tag"></i> Propositions de tags
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_suspicious_deletions' %}active{% endif %}" href="{{ path('app_admin_wiki_suspicious_deletions') }}">
|
||||
<i class="bi bi-exclamation-triangle"></i> Suppressions suspectes
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if app.request.get('_route') == 'app_admin_wiki_missing_translations' %}active{% endif %}" href="{{ path('app_admin_wiki_missing_translations') }}">
|
||||
<i class="bi bi-translate"></i> Pages sans traduction
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki OpenStreetMap</h1>
|
||||
<p class="lead">Comparaison des pages wiki en français et en anglais pour les clés OSM les plus utilisées.</p>
|
||||
|
||||
|
|
|
@ -8,14 +8,29 @@
|
|||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.title-level-1 {
|
||||
font-weight: bold;
|
||||
}
|
||||
.title-level-2 {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
.title-level-3 {
|
||||
padding-left: 2.8rem;
|
||||
}
|
||||
.title-level-4 {
|
||||
padding-left: 4rem;
|
||||
}
|
||||
.title-level-5 {
|
||||
padding-left: 5.2rem;
|
||||
}
|
||||
.title-level-6 {
|
||||
padding-left: 6.4rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Comparaison Wiki OpenStreetMap - {{ key }}</h1>
|
||||
<p class="lead">Comparaison détaillée des pages wiki en français et en anglais pour la clé OSM "{{ key }}".</p>
|
||||
|
||||
|
@ -118,22 +133,20 @@
|
|||
<span class="badge bg-light text-dark">{{ en_page.sections }} sections</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{# <h4>Sections communes ({{ detailed_comparison.section_comparison.common|length }})</h4>#}
|
||||
{# <ul class="list-group mb-3">#}
|
||||
{# {% for section in detailed_comparison.section_comparison.common %}#}
|
||||
{# <li class="list-group-item">#}
|
||||
{# <span class="badge bg-secondary">h{{ section.en.level }}</span>#}
|
||||
{# {{ section.en.title }}#}
|
||||
{# </li>#}
|
||||
{# {% endfor %}#}
|
||||
{# </ul>#}
|
||||
|
||||
<h4>Sections uniquement en anglais ({{ detailed_comparison.section_comparison.en_only|length }})</h4>
|
||||
<ul class="list-group">
|
||||
<h4>Sections alignées par hiérarchie</h4>
|
||||
<ul class="list-group mb-3">
|
||||
{% for section in detailed_comparison.section_comparison.common %}
|
||||
<li class="list-group-item title-level-{{ section.en.level }}">
|
||||
<span class="badge bg-secondary">h{{ section.en.level }}</span>
|
||||
{{ section.en.title }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% for section in detailed_comparison.section_comparison.en_only %}
|
||||
<li class="list-group-item list-group-item-warning title-level-{{ section.level }}">
|
||||
<li class="list-group-item list-group-item-warning title-level-{{ section.level }}">
|
||||
<span class="badge bg-secondary">h{{ section.level }}</span>
|
||||
{{ section.title }}
|
||||
<span class="badge bg-warning text-dark float-end">EN uniquement</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -147,22 +160,20 @@
|
|||
<span class="badge bg-light text-dark">{{ fr_page.sections }} sections</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{# <h4>Sections communes ({{ detailed_comparison.section_comparison.common|length }})</h4>#}
|
||||
{# <ul class="list-group mb-3">#}
|
||||
{# {% for section in detailed_comparison.section_comparison.common %}#}
|
||||
{# <li class="list-group-item">#}
|
||||
{# <span class="badge bg-secondary">h{{ section.fr.level }}</span>#}
|
||||
{# {{ section.fr.title }}#}
|
||||
{# </li>#}
|
||||
{# {% endfor %}#}
|
||||
{# </ul>#}
|
||||
|
||||
<h4>Sections uniquement en français ({{ detailed_comparison.section_comparison.fr_only|length }})</h4>
|
||||
<ul class="list-group">
|
||||
<h4>Sections alignées par hiérarchie</h4>
|
||||
<ul class="list-group mb-3">
|
||||
{% for section in detailed_comparison.section_comparison.common %}
|
||||
<li class="list-group-item title-level-{{ section.fr.level }}">
|
||||
<span class="badge bg-secondary">h{{ section.fr.level }}</span>
|
||||
{{ section.fr.title }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% for section in detailed_comparison.section_comparison.fr_only %}
|
||||
<li class="list-group-item list-group-item-info title-level-{{ section.level }}">
|
||||
<li class="list-group-item list-group-item-info title-level-{{ section.level }}">
|
||||
<span class="badge bg-secondary">h{{ section.level }}</span>
|
||||
{{ section.title }}
|
||||
<span class="badge bg-info float-end">FR uniquement</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
83
templates/admin/wiki_missing_translations.html.twig
Normal file
83
templates/admin/wiki_missing_translations.html.twig
Normal file
|
@ -0,0 +1,83 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Pages Wiki françaises sans traduction anglaise{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki françaises sans traduction anglaise</h1>
|
||||
<p class="lead">Liste des pages françaises du wiki OSM qui n'ont pas de traduction en anglais.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Pages françaises uniquement</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if french_only_pages|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Clé</th>
|
||||
<th>Sections</th>
|
||||
<th>Mots</th>
|
||||
<th>Liens</th>
|
||||
<th>Dernière modification</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key, page in french_only_pages %}
|
||||
<tr>
|
||||
<td><strong>{{ key }}</strong></td>
|
||||
<td>{{ page.sections }}</td>
|
||||
<td>{{ page.word_count }}</td>
|
||||
<td>{{ page.link_count }}</td>
|
||||
<td>{{ page.last_modified }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.url }}" target="_blank" class="btn btn-sm btn-outline-info" title="Version française">
|
||||
<i class="bi bi-translate"></i> FR
|
||||
</a>
|
||||
<a href="https://wiki.openstreetmap.org/wiki/Key:{{ key }}" target="_blank" class="btn btn-sm btn-success" title="Créer la version anglaise">
|
||||
<i class="bi bi-plus-circle"></i> Créer EN
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucune page française sans traduction anglaise n'a été trouvée.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>À propos des pages sans traduction anglaise</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Ces pages sont des contenus originaux créés en français qui n'ont pas encore été traduits en anglais.</p>
|
||||
<p>Bien que la langue principale du wiki OSM soit l'anglais, il est parfois utile de créer d'abord du contenu dans sa langue maternelle, puis de le traduire.</p>
|
||||
<p>Contribuer à la traduction de ces pages en anglais permet de :</p>
|
||||
<ul>
|
||||
<li>Partager les connaissances avec la communauté internationale</li>
|
||||
<li>Améliorer la visibilité des contributions françaises</li>
|
||||
<li>Faciliter la collaboration entre contributeurs de différentes langues</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ path('app_admin_wiki') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
122
templates/admin/wiki_random_suggestion.html.twig
Normal file
122
templates/admin/wiki_random_suggestion.html.twig
Normal file
|
@ -0,0 +1,122 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Suggestion de page Wiki à améliorer{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Suggestion de page Wiki à améliorer</h1>
|
||||
<p class="lead">Voici une page wiki qui a besoin d'être améliorée.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2>{{ page.key }}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info">
|
||||
<h3>Raisons d'amélioration</h3>
|
||||
<p>{{ page.reason }}</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3>Version anglaise</h3>
|
||||
<p class="mb-0">
|
||||
<small>Dernière modification: {{ page.en_page.last_modified }}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Sections
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.sections }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Mots
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.word_count|default(0) }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Liens
|
||||
<span class="badge bg-primary rounded-pill">{{ page.en_page.link_count|default(0) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la page anglaise
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h3>Version française</h3>
|
||||
{% if page.fr_page %}
|
||||
<p class="mb-0">
|
||||
<small>Dernière modification: {{ page.fr_page.last_modified }}</small>
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="mb-0">
|
||||
<small>Page non existante</small>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if page.fr_page %}
|
||||
<ul class="list-group mb-3">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Sections
|
||||
<span class="badge bg-info rounded-pill">{{ page.fr_page.sections }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Mots
|
||||
<span class="badge bg-info rounded-pill">{{ page.fr_page.word_count|default(0) }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
Liens
|
||||
<span class="badge bg-info rounded-pill">{{ page.fr_page.link_count|default(0) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ page.fr_page.url }}" target="_blank" class="btn btn-outline-info">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir la page française
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning">
|
||||
<p><i class="bi bi-exclamation-triangle"></i> <strong>La page wiki pour la clé "{{ page.key }}" n'existe pas en français.</strong></p>
|
||||
<p>Vous pouvez contribuer en créant cette page sur le wiki OpenStreetMap.</p>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="https://wiki.openstreetmap.org/wiki/FR:Key:{{ page.key }}" target="_blank" class="btn btn-success">
|
||||
<i class="bi bi-plus-circle"></i> Créer la page française
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 d-grid gap-2">
|
||||
<a href="{{ path('app_admin_wiki_compare', {'key': page.key}) }}" class="btn btn-primary">
|
||||
<i class="bi bi-arrows-angle-expand"></i> Voir la comparaison détaillée
|
||||
</a>
|
||||
<a href="{{ path('app_admin_wiki_random_suggestion') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-shuffle"></i> Autre suggestion aléatoire
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ path('app_admin_wiki') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
94
templates/admin/wiki_suspicious_deletions.html.twig
Normal file
94
templates/admin/wiki_suspicious_deletions.html.twig
Normal file
|
@ -0,0 +1,94 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Pages Wiki avec suppressions suspectes{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Pages Wiki avec suppressions suspectes</h1>
|
||||
<p class="lead">Pages françaises du wiki OSM qui ont des suppressions suspectes, avec un grand pourcentage de suppression par rapport à la version anglaise.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Pages avec suppressions suspectes</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if suspicious_pages|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Clé</th>
|
||||
<th>Mots (EN)</th>
|
||||
<th>Mots (FR)</th>
|
||||
<th>Différence</th>
|
||||
<th>% Suppression</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for page in suspicious_pages %}
|
||||
<tr>
|
||||
<td><strong>{{ page.key }}</strong></td>
|
||||
<td>{{ page.en_page.word_count }}</td>
|
||||
<td>{{ page.fr_page.word_count }}</td>
|
||||
<td>{{ page.en_page.word_count - page.fr_page.word_count }}</td>
|
||||
<td>
|
||||
{% if page.deletion_percentage > 70 %}
|
||||
<span class="badge bg-danger">{{ page.deletion_percentage }}%</span>
|
||||
{% elseif page.deletion_percentage > 50 %}
|
||||
<span class="badge bg-warning text-dark">{{ page.deletion_percentage }}%</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info">{{ page.deletion_percentage }}%</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{{ page.en_page.url }}" target="_blank" class="btn btn-sm btn-outline-primary" title="Version anglaise">
|
||||
<i class="bi bi-translate"></i> EN
|
||||
</a>
|
||||
<a href="{{ page.fr_page.url }}" target="_blank" class="btn btn-sm btn-outline-info" title="Version française">
|
||||
<i class="bi bi-translate"></i> FR
|
||||
</a>
|
||||
<a href="{{ path('app_admin_wiki_compare', {'key': page.key}) }}" class="btn btn-sm btn-outline-secondary" title="Comparer les versions">
|
||||
<i class="bi bi-arrows-angle-expand"></i> Comparer
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucune page avec suppressions suspectes n'a été trouvée.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>À propos des suppressions suspectes</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Les suppressions suspectes sont identifiées lorsque la version française d'une page wiki contient significativement moins de mots que la version anglaise (plus de 30% de différence).</p>
|
||||
<p>Cela peut indiquer :</p>
|
||||
<ul>
|
||||
<li>Une traduction incomplète</li>
|
||||
<li>Des sections manquantes dans la version française</li>
|
||||
<li>Des mises à jour importantes dans la version anglaise qui n'ont pas été reportées en français</li>
|
||||
</ul>
|
||||
<p>Ces pages sont des candidates prioritaires pour une mise à jour de la traduction française.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ path('app_admin_wiki') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
92
templates/admin/wiki_tag_proposals.html.twig
Normal file
92
templates/admin/wiki_tag_proposals.html.twig
Normal file
|
@ -0,0 +1,92 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Propositions de tags OSM{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container mt-4">
|
||||
{% include 'admin/_wiki_navigation.html.twig' %}
|
||||
|
||||
<h1>Propositions de tags OSM en cours de vote</h1>
|
||||
<p class="lead">Liste des propositions de tags OpenStreetMap actuellement en cours de vote ou de discussion.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>Propositions actives</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if proposals|length > 0 %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th>Fonctionnalité</th>
|
||||
<th>Description</th>
|
||||
<th>Proposé par</th>
|
||||
<th>Statut</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for proposal in proposals %}
|
||||
<tr>
|
||||
<td>{{ proposal.feature }}</td>
|
||||
<td>{{ proposal.description }}</td>
|
||||
<td>{{ proposal.proposer }}</td>
|
||||
<td>
|
||||
{% if 'voting' in proposal.status|lower %}
|
||||
<span class="badge bg-primary">En vote</span>
|
||||
{% elseif 'draft' in proposal.status|lower %}
|
||||
<span class="badge bg-warning text-dark">Brouillon</span>
|
||||
{% elseif 'rfc' in proposal.status|lower %}
|
||||
<span class="badge bg-info">RFC</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ proposal.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ proposal.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p><i class="bi bi-info-circle"></i> Aucune proposition de tag n'a été trouvée.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h2>À propos des propositions de tags</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Les propositions de tags sont un processus communautaire pour introduire de nouveaux tags ou modifier des tags existants dans OpenStreetMap.</p>
|
||||
<p>Le processus typique comprend les étapes suivantes :</p>
|
||||
<ol>
|
||||
<li><strong>Brouillon</strong> : La proposition initiale est rédigée et discutée.</li>
|
||||
<li><strong>RFC (Request for Comments)</strong> : La proposition est ouverte aux commentaires de la communauté.</li>
|
||||
<li><strong>Vote</strong> : La proposition est soumise au vote de la communauté.</li>
|
||||
<li><strong>Approbation ou rejet</strong> : Selon les résultats du vote, la proposition est approuvée ou rejetée.</li>
|
||||
</ol>
|
||||
<p>Vous pouvez participer à ce processus en commentant les propositions ou en votant lorsqu'elles sont en phase de vote.</p>
|
||||
<div class="d-grid gap-2 col-md-6 mx-auto mt-3">
|
||||
<a href="https://wiki.openstreetmap.org/wiki/Proposed_features" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right"></i> Voir toutes les propositions sur le wiki OSM
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ path('app_admin_wiki') }}" class="btn btn-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Retour à la liste des pages wiki
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,14 +1,31 @@
|
|||
# OSM Wiki Compare
|
||||
|
||||
Ce projet contient des scripts pour analyser les pages wiki d'OpenStreetMap, identifier celles qui ont besoin de mises à jour ou de traductions, et publier des suggestions sur Mastodon pour encourager la communauté à contribuer.
|
||||
Ce projet contient des scripts pour analyser les pages wiki d'OpenStreetMap, identifier celles qui ont besoin de mises à
|
||||
jour ou de traductions, et publier des suggestions sur Mastodon pour encourager la communauté à contribuer.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Le projet comprend trois scripts principaux :
|
||||
Le projet comprend huit scripts principaux :
|
||||
|
||||
1. **wiki_compare.py** : Récupère les 10 clés OSM les plus utilisées, compare leurs pages wiki en anglais et en français, et identifie celles qui ont besoin de mises à jour.
|
||||
2. **post_outdated_page.py** : Sélectionne aléatoirement une page wiki française qui n'est pas à jour et publie un message sur Mastodon pour suggérer sa mise à jour.
|
||||
3. **suggest_translation.py** : Identifie les pages wiki anglaises qui n'ont pas de traduction française et publie une suggestion de traduction sur Mastodon.
|
||||
1. **wiki_compare.py** : Récupère les 10 clés OSM les plus utilisées, compare leurs pages wiki en anglais et en
|
||||
français, et identifie celles qui ont besoin de mises à jour.
|
||||
2. **post_outdated_page.py** : Sélectionne aléatoirement une page wiki française qui n'est pas à jour et publie un
|
||||
message sur Mastodon pour suggérer sa mise à jour.
|
||||
3. **suggest_translation.py** : Identifie les pages wiki anglaises qui n'ont pas de traduction française et publie une
|
||||
suggestion de traduction sur Mastodon.
|
||||
4. **detect_suspicious_deletions.py** : Analyse les changements récents du wiki OSM pour détecter les suppressions
|
||||
suspectes (plus de 20 caractères) et les enregistre dans un fichier JSON pour affichage sur le site web.
|
||||
5. **fetch_proposals.py** : Récupère les propositions de tags OSM en cours de vote et les propositions récemment modifiées,
|
||||
et les enregistre dans un fichier JSON pour affichage sur le site web. Les données sont mises en cache pendant une heure
|
||||
pour éviter des requêtes trop fréquentes au serveur wiki.
|
||||
6. **find_untranslated_french_pages.py** : Identifie les pages wiki françaises qui n'ont pas de traduction en anglais
|
||||
et les enregistre dans un fichier JSON pour affichage sur le site web. Les données sont mises en cache pendant une heure.
|
||||
7. **find_pages_unavailable_in_french.py** : Scrape la catégorie des pages non disponibles en français, gère la pagination
|
||||
pour récupérer toutes les pages, les groupe par préfixe de langue et priorise les pages commençant par "En:". Les données
|
||||
sont mises en cache pendant une heure.
|
||||
8. **fetch_osm_fr_groups.py** : Récupère les informations sur les groupes de travail et les groupes locaux d'OSM-FR
|
||||
depuis la section #Pages_des_groupes_locaux et les enregistre dans un fichier JSON pour affichage sur le site web.
|
||||
Les données sont mises en cache pendant une heure.
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -60,6 +77,7 @@ Pour analyser les pages wiki et générer les fichiers de données :
|
|||
```
|
||||
|
||||
Cela produira :
|
||||
|
||||
- `top_keys.json` : Les 10 clés OSM les plus utilisées
|
||||
- `wiki_pages.csv` : Informations sur chaque page wiki
|
||||
- `outdated_pages.json` : Pages qui ont besoin de mises à jour
|
||||
|
@ -93,17 +111,124 @@ Pour simuler la publication sans réellement poster sur Mastodon (mode test) :
|
|||
./suggest_translation.py --dry-run
|
||||
```
|
||||
|
||||
### Détecter les suppressions suspectes
|
||||
|
||||
Pour analyser les changements récents du wiki OSM et détecter les suppressions suspectes :
|
||||
|
||||
```bash
|
||||
./detect_suspicious_deletions.py
|
||||
```
|
||||
|
||||
Pour afficher les suppressions détectées sans les enregistrer dans un fichier (mode test) :
|
||||
|
||||
```bash
|
||||
./detect_suspicious_deletions.py --dry-run
|
||||
```
|
||||
|
||||
### Récupérer les propositions de tags
|
||||
|
||||
Pour récupérer les propositions de tags OSM en cours de vote et récemment modifiées :
|
||||
|
||||
```bash
|
||||
./fetch_proposals.py
|
||||
```
|
||||
|
||||
Pour forcer la mise à jour des données même si le cache est encore frais :
|
||||
|
||||
```bash
|
||||
./fetch_proposals.py --force
|
||||
```
|
||||
|
||||
Pour afficher les propositions sans les enregistrer dans un fichier (mode test) :
|
||||
|
||||
```bash
|
||||
./fetch_proposals.py --dry-run
|
||||
```
|
||||
|
||||
### Trouver les pages françaises sans traduction anglaise
|
||||
|
||||
Pour identifier les pages wiki françaises qui n'ont pas de traduction en anglais :
|
||||
|
||||
```bash
|
||||
./find_untranslated_french_pages.py
|
||||
```
|
||||
|
||||
Pour forcer la mise à jour des données même si le cache est encore frais :
|
||||
|
||||
```bash
|
||||
./find_untranslated_french_pages.py --force
|
||||
```
|
||||
|
||||
Pour afficher les pages sans les enregistrer dans un fichier (mode test) :
|
||||
|
||||
```bash
|
||||
./find_untranslated_french_pages.py --dry-run
|
||||
```
|
||||
|
||||
### Trouver les pages non disponibles en français
|
||||
|
||||
Pour identifier les pages wiki qui n'ont pas de traduction française, groupées par langue d'origine :
|
||||
|
||||
```bash
|
||||
./find_pages_unavailable_in_french.py
|
||||
```
|
||||
|
||||
Pour forcer la mise à jour des données même si le cache est encore frais :
|
||||
|
||||
```bash
|
||||
./find_pages_unavailable_in_french.py --force
|
||||
```
|
||||
|
||||
Pour afficher les pages sans les enregistrer dans un fichier (mode test) :
|
||||
|
||||
```bash
|
||||
./find_pages_unavailable_in_french.py --dry-run
|
||||
```
|
||||
|
||||
### Récupérer les groupes OSM-FR
|
||||
|
||||
Pour récupérer les informations sur les groupes de travail et les groupes locaux d'OSM-FR :
|
||||
|
||||
```bash
|
||||
./fetch_osm_fr_groups.py
|
||||
```
|
||||
|
||||
Pour forcer la mise à jour des données même si le cache est encore frais :
|
||||
|
||||
```bash
|
||||
./fetch_osm_fr_groups.py --force
|
||||
```
|
||||
|
||||
Pour afficher les groupes sans les enregistrer dans un fichier (mode test) :
|
||||
|
||||
```bash
|
||||
./fetch_osm_fr_groups.py --dry-run
|
||||
```
|
||||
|
||||
## Automatisation
|
||||
|
||||
Vous pouvez automatiser l'exécution de ces scripts à l'aide de cron pour publier régulièrement des suggestions de mises à jour et de traductions.
|
||||
Vous pouvez automatiser l'exécution de ces scripts à l'aide de cron pour publier régulièrement des suggestions de mises
|
||||
à jour et de traductions, ainsi que pour maintenir à jour les données affichées sur le site web.
|
||||
|
||||
Exemple de configuration cron pour publier une suggestion de mise à jour chaque lundi et une suggestion de traduction chaque jeudi :
|
||||
Exemple de configuration cron pour publier des suggestions et mettre à jour les données :
|
||||
|
||||
```
|
||||
# Publier des suggestions sur Mastodon
|
||||
0 10 * * 1 cd /chemin/vers/wiki_compare && ./wiki_compare.py && ./post_outdated_page.py
|
||||
0 10 * * 4 cd /chemin/vers/wiki_compare && ./wiki_compare.py && ./suggest_translation.py
|
||||
|
||||
# Mettre à jour les données pour le site web (toutes les 6 heures)
|
||||
0 */6 * * * cd /chemin/vers/wiki_compare && ./detect_suspicious_deletions.py
|
||||
0 */6 * * * cd /chemin/vers/wiki_compare && ./fetch_proposals.py
|
||||
0 */6 * * * cd /chemin/vers/wiki_compare && ./find_untranslated_french_pages.py
|
||||
0 */6 * * * cd /chemin/vers/wiki_compare && ./find_pages_unavailable_in_french.py
|
||||
0 */6 * * * cd /chemin/vers/wiki_compare && ./fetch_osm_fr_groups.py
|
||||
```
|
||||
|
||||
Note : Les scripts de mise à jour des données pour le site web intègrent déjà une vérification de fraîcheur du cache (1 heure),
|
||||
mais la configuration cron ci-dessus permet de s'assurer que les données sont régulièrement mises à jour même en cas de problème
|
||||
temporaire avec les scripts.
|
||||
|
||||
## Structure des données
|
||||
|
||||
### top_keys.json
|
||||
|
@ -115,8 +240,7 @@ Contient les 10 clés OSM les plus utilisées avec leur nombre d'utilisations :
|
|||
{
|
||||
"key": "building",
|
||||
"count": 459876543
|
||||
},
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
|
@ -140,8 +264,8 @@ Contient des informations détaillées sur les pages qui ont besoin de mises à
|
|||
{
|
||||
"key": "building",
|
||||
"reason": "French page outdated by 491 days",
|
||||
"en_page": { ... },
|
||||
"fr_page": { ... },
|
||||
"en_page": {},
|
||||
"fr_page": {},
|
||||
"date_diff": 491,
|
||||
"word_diff": 700,
|
||||
"section_diff": 2,
|
||||
|
@ -150,30 +274,209 @@ Contient des informations détaillées sur les pages qui ont besoin de mises à
|
|||
{
|
||||
"key": "amenity",
|
||||
"reason": "French page missing",
|
||||
"en_page": { ... },
|
||||
"en_page": {},
|
||||
"fr_page": null,
|
||||
"date_diff": 0,
|
||||
"word_diff": 4200,
|
||||
"section_diff": 15,
|
||||
"priority": 100
|
||||
},
|
||||
...
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### suspicious_deletions.json
|
||||
|
||||
Contient des informations sur les suppressions suspectes détectées dans les changements récents du wiki OSM :
|
||||
|
||||
```json
|
||||
{
|
||||
"last_updated": "2025-08-22T15:03:03.616532",
|
||||
"deletions": [
|
||||
{
|
||||
"page_title": "FR:Key:roof:shape",
|
||||
"page_url": "https://wiki.openstreetmap.org/wiki/FR:Key:roof:shape",
|
||||
"deletion_size": -286,
|
||||
"timestamp": "22 août 2025 à 14:15",
|
||||
"user": "RubenKelevra",
|
||||
"comment": "Suppression de contenu obsolète"
|
||||
},
|
||||
{
|
||||
"page_title": "FR:Key:sport",
|
||||
"page_url": "https://wiki.openstreetmap.org/wiki/FR:Key:sport",
|
||||
"deletion_size": -240,
|
||||
"timestamp": "21 août 2025 à 09:30",
|
||||
"user": "Computae",
|
||||
"comment": "Mise à jour de la documentation"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### proposals.json
|
||||
|
||||
Contient des informations sur les propositions de tags OSM en cours de vote et récemment modifiées :
|
||||
|
||||
```json
|
||||
{
|
||||
"last_updated": "2025-08-22T15:09:49.905332",
|
||||
"voting_proposals": [
|
||||
{
|
||||
"title": "Proposal:Man made=ceremonial gate",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Proposal:Man_made%3Dceremonial_gate",
|
||||
"status": "Voting",
|
||||
"type": "voting"
|
||||
},
|
||||
{
|
||||
"title": "Proposal:Developer",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Proposal:Developer",
|
||||
"status": "Voting",
|
||||
"type": "voting"
|
||||
}
|
||||
],
|
||||
"recent_proposals": [
|
||||
{
|
||||
"title": "Proposal:Landuse=brownfield",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Proposal:Landuse=brownfield",
|
||||
"last_modified": "22 août 2025 à 10:45",
|
||||
"modified_by": "MapperUser",
|
||||
"type": "recent"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### untranslated_french_pages.json
|
||||
|
||||
Contient des informations sur les pages wiki françaises qui n'ont pas de traduction en anglais :
|
||||
|
||||
```json
|
||||
{
|
||||
"last_updated": "2025-08-22T16:30:15.123456",
|
||||
"untranslated_pages": [
|
||||
{
|
||||
"title": "FR:Key:building:colour",
|
||||
"key": "Key:building:colour",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Key:building:colour",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Tag:amenity=bicycle_repair_station",
|
||||
"key": "Tag:amenity=bicycle_repair_station",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Tag:amenity=bicycle_repair_station",
|
||||
"has_translation": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### pages_unavailable_in_french.json
|
||||
|
||||
Contient des informations sur les pages wiki qui n'ont pas de traduction française, groupées par langue d'origine :
|
||||
|
||||
```json
|
||||
{
|
||||
"last_updated": "2025-08-22T17:15:45.123456",
|
||||
"grouped_pages": {
|
||||
"En": [
|
||||
{
|
||||
"title": "En:Key:building:colour",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/En:Key:building:colour",
|
||||
"language_prefix": "En",
|
||||
"is_english": true,
|
||||
"priority": 1
|
||||
}
|
||||
],
|
||||
"De": [
|
||||
{
|
||||
"title": "De:Tag:highway=residential",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/De:Tag:highway=residential",
|
||||
"language_prefix": "De",
|
||||
"is_english": false,
|
||||
"priority": 0
|
||||
}
|
||||
],
|
||||
"Other": [
|
||||
{
|
||||
"title": "Tag:amenity=bicycle_repair_station",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Tag:amenity=bicycle_repair_station",
|
||||
"language_prefix": "Other",
|
||||
"is_english": false,
|
||||
"priority": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"all_pages": [
|
||||
{
|
||||
"title": "En:Key:building:colour",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/En:Key:building:colour",
|
||||
"language_prefix": "En",
|
||||
"is_english": true,
|
||||
"priority": 1
|
||||
},
|
||||
{
|
||||
"title": "De:Tag:highway=residential",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/De:Tag:highway=residential",
|
||||
"language_prefix": "De",
|
||||
"is_english": false,
|
||||
"priority": 0
|
||||
},
|
||||
{
|
||||
"title": "Tag:amenity=bicycle_repair_station",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Tag:amenity=bicycle_repair_station",
|
||||
"language_prefix": "Other",
|
||||
"is_english": false,
|
||||
"priority": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### osm_fr_groups.json
|
||||
|
||||
Contient des informations sur les groupes de travail et les groupes locaux d'OSM-FR :
|
||||
|
||||
```json
|
||||
{
|
||||
"last_updated": "2025-08-22T16:45:30.789012",
|
||||
"working_groups": [
|
||||
{
|
||||
"name": "Groupe Bâtiments",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail/B%C3%A2timents",
|
||||
"description": "Groupe de travail sur la cartographie des bâtiments",
|
||||
"category": "Cartographie",
|
||||
"type": "working_group"
|
||||
}
|
||||
],
|
||||
"local_groups": [
|
||||
{
|
||||
"name": "Groupe local de Paris",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/Paris",
|
||||
"description": "Groupe local des contributeurs parisiens",
|
||||
"type": "local_group"
|
||||
}
|
||||
],
|
||||
"umap_url": "https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488"
|
||||
}
|
||||
```
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Problèmes courants
|
||||
|
||||
1. **Erreur d'authentification Mastodon** : Vérifiez que la variable d'environnement `MASTODON_ACCESS_TOKEN` est correctement définie et que le jeton est valide.
|
||||
1. **Erreur d'authentification Mastodon** : Vérifiez que la variable d'environnement `MASTODON_ACCESS_TOKEN` est
|
||||
correctement définie et que le jeton est valide.
|
||||
|
||||
2. **Erreur de chargement des fichiers JSON** : Assurez-vous d'exécuter `wiki_compare.py` avant les autres scripts pour générer les fichiers de données nécessaires.
|
||||
2. **Erreur de chargement des fichiers JSON** : Assurez-vous d'exécuter `wiki_compare.py` avant les autres scripts pour
|
||||
générer les fichiers de données nécessaires.
|
||||
|
||||
3. **Aucune page à mettre à jour ou à traduire** : Il est possible que toutes les pages soient à jour ou traduites. Essayez d'augmenter le nombre de clés analysées en modifiant la valeur `limit` dans la fonction `fetch_top_keys` de `wiki_compare.py`.
|
||||
3. **Aucune page à mettre à jour ou à traduire** : Il est possible que toutes les pages soient à jour ou traduites.
|
||||
Essayez d'augmenter le nombre de clés analysées en modifiant la valeur `limit` dans la fonction `fetch_top_keys` de
|
||||
`wiki_compare.py`.
|
||||
|
||||
### Journalisation
|
||||
|
||||
Tous les scripts utilisent le module `logging` pour enregistrer les informations d'exécution. Par défaut, les logs sont affichés dans la console. Pour les rediriger vers un fichier, modifiez la configuration de logging dans chaque script.
|
||||
Tous les scripts utilisent le module `logging` pour enregistrer les informations d'exécution. Par défaut, les logs sont
|
||||
affichés dans la console. Pour les rediriger vers un fichier, modifiez la configuration de logging dans chaque script.
|
||||
|
||||
## Contribution
|
||||
|
||||
|
|
252
wiki_compare/detect_suspicious_deletions.py
Executable file
252
wiki_compare/detect_suspicious_deletions.py
Executable file
|
@ -0,0 +1,252 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlparse, parse_qs, urlencode
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# URL for recent changes in OSM Wiki (namespace 202 is for Tag pages)
|
||||
RECENT_CHANGES_URL = "https://wiki.openstreetmap.org/w/index.php?hidebots=1&hidenewpages=1&hidecategorization=1&hideWikibase=1&hidelog=1&hidenewuserlog=1&namespace=202&limit=250&days=30&enhanced=1&title=Special:RecentChanges&urlversion=2"
|
||||
|
||||
# Threshold for suspicious deletions (percentage of total content)
|
||||
DELETION_THRESHOLD_PERCENT = 5.0
|
||||
|
||||
# Base URL for OSM Wiki
|
||||
WIKI_BASE_URL = "https://wiki.openstreetmap.org"
|
||||
|
||||
def fetch_recent_changes():
|
||||
"""
|
||||
Fetch the recent changes page from OSM Wiki
|
||||
"""
|
||||
logger.info(f"Fetching recent changes from {RECENT_CHANGES_URL}")
|
||||
try:
|
||||
response = requests.get(RECENT_CHANGES_URL)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching recent changes: {e}")
|
||||
return None
|
||||
|
||||
def fetch_page_content(page_title):
|
||||
"""
|
||||
Fetch the content of a wiki page to count characters
|
||||
"""
|
||||
url = f"{WIKI_BASE_URL}/wiki/{page_title}"
|
||||
logger.info(f"Fetching page content from {url}")
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching page content: {e}")
|
||||
return None
|
||||
|
||||
def count_page_characters(html_content):
|
||||
"""
|
||||
Count the total number of characters in the wiki page content
|
||||
"""
|
||||
if not html_content:
|
||||
return 0
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
# Find the main content div
|
||||
content_div = soup.select_one('#mw-content-text')
|
||||
if not content_div:
|
||||
return 0
|
||||
|
||||
# Get all text content
|
||||
text_content = content_div.get_text(strip=True)
|
||||
|
||||
# Count characters
|
||||
char_count = len(text_content)
|
||||
logger.info(f"Page has {char_count} characters")
|
||||
|
||||
return char_count
|
||||
|
||||
def generate_diff_url(page_title, oldid):
|
||||
"""
|
||||
Generate URL to view the diff of a specific revision
|
||||
"""
|
||||
return f"{WIKI_BASE_URL}/w/index.php?title={page_title}&diff=prev&oldid={oldid}"
|
||||
|
||||
def generate_history_url(page_title):
|
||||
"""
|
||||
Generate URL to view the history of a page
|
||||
"""
|
||||
return f"{WIKI_BASE_URL}/w/index.php?title={page_title}&action=history"
|
||||
|
||||
def load_existing_deletions():
|
||||
"""
|
||||
Load existing suspicious deletions from the JSON file
|
||||
"""
|
||||
output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'suspicious_deletions.json')
|
||||
existing_pages = set()
|
||||
|
||||
try:
|
||||
if os.path.exists(output_file):
|
||||
with open(output_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
if 'deletions' in data:
|
||||
for deletion in data['deletions']:
|
||||
if 'page_title' in deletion:
|
||||
existing_pages.add(deletion['page_title'])
|
||||
logger.info(f"Loaded {len(existing_pages)} existing pages from {output_file}")
|
||||
else:
|
||||
logger.info(f"No existing file found at {output_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading existing deletions: {e}")
|
||||
|
||||
return existing_pages
|
||||
|
||||
def parse_suspicious_deletions(html_content):
|
||||
"""
|
||||
Parse the HTML content to find suspicious deletions
|
||||
"""
|
||||
if not html_content:
|
||||
return []
|
||||
|
||||
# Load existing pages from the JSON file
|
||||
existing_pages = load_existing_deletions()
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
suspicious_deletions = []
|
||||
|
||||
# Find all change list lines
|
||||
change_lines = soup.select('.mw-changeslist .mw-changeslist-line')
|
||||
logger.info(f"Found {len(change_lines)} change lines to analyze")
|
||||
|
||||
for line in change_lines:
|
||||
# Look for deletion indicators
|
||||
deletion_indicator = line.select_one('.mw-plusminus-neg')
|
||||
if deletion_indicator:
|
||||
# Extract the deletion size
|
||||
deletion_text = deletion_indicator.text.strip()
|
||||
try:
|
||||
# Remove any non-numeric characters except minus sign
|
||||
deletion_size = int(''.join(c for c in deletion_text if c.isdigit() or c == '-'))
|
||||
|
||||
# Skip if deletion size is not greater than 100 characters
|
||||
if abs(deletion_size) <= 100:
|
||||
logger.info(f"Skipping deletion with size {deletion_size} (not > 100 characters)")
|
||||
continue
|
||||
|
||||
# Get the page title and URL
|
||||
title_element = line.select_one('.mw-changeslist-title')
|
||||
if title_element:
|
||||
page_title = title_element.text.strip()
|
||||
|
||||
# Skip if page is already in the JSON file
|
||||
if page_title in existing_pages:
|
||||
logger.info(f"Skipping {page_title} (already in JSON file)")
|
||||
continue
|
||||
|
||||
page_url = title_element.get('href', '')
|
||||
if not page_url.startswith('http'):
|
||||
page_url = f"{WIKI_BASE_URL}{page_url}"
|
||||
|
||||
# Extract oldid from the URL if available
|
||||
oldid = None
|
||||
if 'oldid=' in page_url:
|
||||
parsed_url = urlparse(page_url)
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
if 'oldid' in query_params:
|
||||
oldid = query_params['oldid'][0]
|
||||
|
||||
# Fetch the page content to count characters
|
||||
page_html = fetch_page_content(page_title)
|
||||
total_chars = count_page_characters(page_html)
|
||||
|
||||
# Calculate deletion percentage
|
||||
deletion_percentage = 0
|
||||
if total_chars > 0:
|
||||
deletion_percentage = (abs(deletion_size) / total_chars) * 100
|
||||
|
||||
# If deletion percentage is significant
|
||||
if deletion_percentage > DELETION_THRESHOLD_PERCENT:
|
||||
# Get the timestamp
|
||||
timestamp_element = line.select_one('.mw-changeslist-date')
|
||||
timestamp = timestamp_element.text.strip() if timestamp_element else ""
|
||||
|
||||
# Get the user who made the change
|
||||
user_element = line.select_one('.mw-userlink')
|
||||
user = user_element.text.strip() if user_element else "Unknown"
|
||||
|
||||
# Get the comment if available
|
||||
comment_element = line.select_one('.comment')
|
||||
comment = comment_element.text.strip() if comment_element else ""
|
||||
|
||||
# Generate diff and history URLs
|
||||
diff_url = generate_diff_url(page_title, oldid) if oldid else ""
|
||||
history_url = generate_history_url(page_title)
|
||||
|
||||
suspicious_deletions.append({
|
||||
'page_title': page_title,
|
||||
'page_url': page_url,
|
||||
'diff_url': diff_url,
|
||||
'history_url': history_url,
|
||||
'deletion_size': deletion_size,
|
||||
'total_chars': total_chars,
|
||||
'deletion_percentage': round(deletion_percentage, 2),
|
||||
'timestamp': timestamp,
|
||||
'user': user,
|
||||
'comment': comment
|
||||
})
|
||||
logger.info(f"Found suspicious deletion: {page_title} ({deletion_size} chars, {deletion_percentage:.2f}% of content)")
|
||||
except ValueError:
|
||||
logger.warning(f"Could not parse deletion size from: {deletion_text}")
|
||||
|
||||
return suspicious_deletions
|
||||
|
||||
def save_suspicious_deletions(suspicious_deletions):
|
||||
"""
|
||||
Save the suspicious deletions to a JSON file
|
||||
"""
|
||||
output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'suspicious_deletions.json')
|
||||
|
||||
# Add timestamp to the data
|
||||
data = {
|
||||
'last_updated': datetime.now().isoformat(),
|
||||
'deletions': suspicious_deletions
|
||||
}
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Saved {len(suspicious_deletions)} suspicious deletions to {output_file}")
|
||||
return output_file
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Detect suspicious deletions in OSM Wiki recent changes')
|
||||
parser.add_argument('--dry-run', action='store_true', help='Print results without saving to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
html_content = fetch_recent_changes()
|
||||
if html_content:
|
||||
suspicious_deletions = parse_suspicious_deletions(html_content)
|
||||
|
||||
if args.dry_run:
|
||||
logger.info(f"Found {len(suspicious_deletions)} suspicious deletions:")
|
||||
for deletion in suspicious_deletions:
|
||||
logger.info(f"- {deletion['page_title']}: {deletion['deletion_size']} chars by {deletion['user']}")
|
||||
else:
|
||||
output_file = save_suspicious_deletions(suspicious_deletions)
|
||||
logger.info(f"Results saved to {output_file}")
|
||||
else:
|
||||
logger.error("Failed to fetch recent changes. Exiting.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
316
wiki_compare/fetch_osm_fr_groups.py
Executable file
316
wiki_compare/fetch_osm_fr_groups.py
Executable file
|
@ -0,0 +1,316 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
fetch_osm_fr_groups.py
|
||||
|
||||
This script scrapes the OpenStreetMap wiki page for France/OSM-FR to extract
|
||||
information about local working groups. It specifically targets links in the
|
||||
#Pages_des_groupes_locaux section.
|
||||
|
||||
Usage:
|
||||
python fetch_osm_fr_groups.py [--dry-run] [--force]
|
||||
|
||||
Options:
|
||||
--dry-run Run the script without saving the results to a file
|
||||
--force Force update even if the cache is still fresh (less than 1 hour old)
|
||||
|
||||
Output:
|
||||
- osm_fr_groups.json: JSON file with information about OSM-FR local groups
|
||||
- Log messages about the scraping process and results
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constants
|
||||
OUTPUT_FILE = "osm_fr_groups.json"
|
||||
BASE_URL = "https://wiki.openstreetmap.org/wiki/France/OSM-FR"
|
||||
WIKI_BASE_URL = "https://wiki.openstreetmap.org"
|
||||
CACHE_DURATION = timedelta(hours=1) # Cache duration of 1 hour
|
||||
|
||||
def is_cache_fresh():
|
||||
"""
|
||||
Check if the cache file exists and is less than CACHE_DURATION old
|
||||
|
||||
Returns:
|
||||
bool: True if cache is fresh, False otherwise
|
||||
"""
|
||||
if not os.path.exists(OUTPUT_FILE):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(OUTPUT_FILE, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
last_updated = datetime.fromisoformat(data.get('last_updated', '2000-01-01T00:00:00'))
|
||||
now = datetime.now()
|
||||
return (now - last_updated) < CACHE_DURATION
|
||||
except (IOError, json.JSONDecodeError, ValueError) as e:
|
||||
logger.error(f"Error checking cache freshness: {e}")
|
||||
return False
|
||||
|
||||
def get_page_content(url):
|
||||
"""
|
||||
Get the HTML content of a page
|
||||
|
||||
Args:
|
||||
url (str): URL to fetch
|
||||
|
||||
Returns:
|
||||
str: HTML content of the page or None if request failed
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching {url}: {e}")
|
||||
return None
|
||||
|
||||
def extract_working_groups(html_content):
|
||||
"""
|
||||
Extract working groups from the wiki page HTML
|
||||
|
||||
Args:
|
||||
html_content (str): HTML content of the wiki page
|
||||
|
||||
Returns:
|
||||
list: List of working group dictionaries
|
||||
"""
|
||||
if not html_content:
|
||||
return []
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
working_groups = []
|
||||
|
||||
# Find the working groups section
|
||||
working_groups_section = None
|
||||
for heading in soup.find_all(['h2', 'h3']):
|
||||
if heading.get_text().strip() == 'Groupes de travail' or 'Groupes_de_travail' in heading.get_text():
|
||||
working_groups_section = heading
|
||||
break
|
||||
|
||||
if not working_groups_section:
|
||||
logger.warning("Could not find working groups section")
|
||||
# Return an empty list but with a default category
|
||||
return []
|
||||
|
||||
# Get the content following the heading until the next heading
|
||||
current = working_groups_section.next_sibling
|
||||
while current and not current.name in ['h2', 'h3']:
|
||||
if current.name == 'ul':
|
||||
# Process list items
|
||||
for li in current.find_all('li', recursive=False):
|
||||
link = li.find('a')
|
||||
if link:
|
||||
name = link.get_text().strip()
|
||||
url = WIKI_BASE_URL + link.get('href') if link.get('href').startswith('/') else link.get('href')
|
||||
|
||||
# Extract description (text after the link)
|
||||
description = ""
|
||||
next_node = link.next_sibling
|
||||
while next_node:
|
||||
if isinstance(next_node, str):
|
||||
description += next_node.strip()
|
||||
next_node = next_node.next_sibling if hasattr(next_node, 'next_sibling') else None
|
||||
|
||||
description = description.strip(' :-,')
|
||||
|
||||
working_groups.append({
|
||||
"name": name,
|
||||
"url": url,
|
||||
"description": description,
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
})
|
||||
current = current.next_sibling
|
||||
|
||||
logger.info(f"Found {len(working_groups)} working groups")
|
||||
return working_groups
|
||||
|
||||
def extract_local_groups(html_content):
|
||||
"""
|
||||
Extract local groups from the wiki page HTML
|
||||
|
||||
Args:
|
||||
html_content (str): HTML content of the wiki page
|
||||
|
||||
Returns:
|
||||
list: List of local group dictionaries
|
||||
"""
|
||||
if not html_content:
|
||||
return []
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
local_groups = []
|
||||
|
||||
# Find the local groups section
|
||||
local_groups_section = None
|
||||
for heading in soup.find_all(['h2', 'h3']):
|
||||
if heading.get_text().strip() == 'Groupes locaux' or 'Pages des groupes locaux' in heading.get_text():
|
||||
local_groups_section = heading
|
||||
break
|
||||
|
||||
if not local_groups_section:
|
||||
logger.warning("Could not find local groups section")
|
||||
return []
|
||||
|
||||
# Get the content following the heading until the next heading
|
||||
current = local_groups_section.next_sibling
|
||||
while current and not current.name in ['h2', 'h3']:
|
||||
if current.name == 'ul':
|
||||
# Process list items
|
||||
for li in current.find_all('li', recursive=False):
|
||||
link = li.find('a')
|
||||
if link:
|
||||
name = link.get_text().strip()
|
||||
url = WIKI_BASE_URL + link.get('href') if link.get('href').startswith('/') else link.get('href')
|
||||
|
||||
# Extract description (text after the link)
|
||||
description = ""
|
||||
next_node = link.next_sibling
|
||||
while next_node:
|
||||
if isinstance(next_node, str):
|
||||
description += next_node.strip()
|
||||
next_node = next_node.next_sibling if hasattr(next_node, 'next_sibling') else None
|
||||
|
||||
description = description.strip(' :-,')
|
||||
|
||||
local_groups.append({
|
||||
"name": name,
|
||||
"url": url,
|
||||
"description": description,
|
||||
"type": "local_group"
|
||||
})
|
||||
current = current.next_sibling
|
||||
|
||||
logger.info(f"Found {len(local_groups)} local groups")
|
||||
return local_groups
|
||||
|
||||
def extract_umap_url(html_content):
|
||||
"""
|
||||
Extract the uMap URL for OSM-FR local groups
|
||||
|
||||
Args:
|
||||
html_content (str): HTML content of the wiki page
|
||||
|
||||
Returns:
|
||||
str: uMap URL or None if not found
|
||||
"""
|
||||
if not html_content:
|
||||
return None
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
# Look for links to umap.openstreetmap.fr
|
||||
for link in soup.find_all('a'):
|
||||
href = link.get('href', '')
|
||||
if 'umap.openstreetmap.fr' in href and 'groupes-locaux' in href:
|
||||
return href
|
||||
|
||||
return None
|
||||
|
||||
def save_results(local_groups, working_groups, umap_url, dry_run=False):
|
||||
"""
|
||||
Save the results to a JSON file
|
||||
|
||||
Args:
|
||||
local_groups (list): List of local group dictionaries
|
||||
working_groups (list): List of working group dictionaries
|
||||
umap_url (str): URL to the uMap for local groups
|
||||
dry_run (bool): If True, don't actually save to file
|
||||
|
||||
Returns:
|
||||
bool: True if saving was successful or dry run, False otherwise
|
||||
"""
|
||||
if dry_run:
|
||||
logger.info("DRY RUN: Would have saved results to file")
|
||||
logger.info(f"Local groups: {len(local_groups)}")
|
||||
for group in local_groups:
|
||||
logger.info(f" - {group['name']}: {group['url']}")
|
||||
logger.info(f"Working groups: {len(working_groups)}")
|
||||
for group in working_groups:
|
||||
logger.info(f" - {group['name']}: {group['url']}")
|
||||
if umap_url:
|
||||
logger.info(f"uMap URL: {umap_url}")
|
||||
return True
|
||||
|
||||
# Prepare the data structure
|
||||
data = {
|
||||
"last_updated": datetime.now().isoformat(),
|
||||
"local_groups": local_groups,
|
||||
"working_groups": working_groups,
|
||||
"umap_url": umap_url
|
||||
}
|
||||
|
||||
try:
|
||||
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
logger.info(f"Successfully saved {len(local_groups)} local groups and {len(working_groups)} working groups to {OUTPUT_FILE}")
|
||||
return True
|
||||
except IOError as e:
|
||||
logger.error(f"Error saving results to {OUTPUT_FILE}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to execute the script"""
|
||||
parser = argparse.ArgumentParser(description="Scrape OSM-FR local groups from the wiki")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Run without saving results to file")
|
||||
parser.add_argument("--force", action="store_true", help="Force update even if cache is fresh")
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("Starting fetch_osm_fr_groups.py")
|
||||
|
||||
# Check if cache is fresh
|
||||
if is_cache_fresh() and not args.force:
|
||||
logger.info(f"Cache is still fresh (less than {CACHE_DURATION.total_seconds()/3600} hours old)")
|
||||
logger.info(f"Use --force to update anyway")
|
||||
return
|
||||
|
||||
# Get the wiki page content
|
||||
html_content = get_page_content(BASE_URL)
|
||||
|
||||
if not html_content:
|
||||
logger.error("Failed to get wiki page content")
|
||||
return
|
||||
|
||||
# Extract local groups
|
||||
local_groups = extract_local_groups(html_content)
|
||||
|
||||
if not local_groups:
|
||||
logger.warning("No local groups found")
|
||||
|
||||
# Extract working groups
|
||||
working_groups = extract_working_groups(html_content)
|
||||
|
||||
if not working_groups:
|
||||
logger.warning("No working groups found")
|
||||
# Initialize with an empty list to avoid errors in the controller
|
||||
working_groups = []
|
||||
|
||||
# Extract uMap URL
|
||||
umap_url = extract_umap_url(html_content)
|
||||
|
||||
# Save results
|
||||
success = save_results(local_groups, working_groups, umap_url, args.dry_run)
|
||||
|
||||
if success:
|
||||
logger.info("Script completed successfully")
|
||||
else:
|
||||
logger.error("Script completed with errors")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
183
wiki_compare/fetch_proposals.py
Executable file
183
wiki_compare/fetch_proposals.py
Executable file
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# URLs for OSM Wiki proposals
|
||||
VOTING_PROPOSALS_URL = "https://wiki.openstreetmap.org/wiki/Category:Proposals_with_%22Voting%22_status"
|
||||
RECENT_CHANGES_URL = "https://wiki.openstreetmap.org/w/index.php?title=Special:RecentChanges&namespace=102&limit=50" # Namespace 102 is for Proposal pages
|
||||
|
||||
# Output file
|
||||
OUTPUT_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'proposals.json')
|
||||
|
||||
# Cache timeout (in hours)
|
||||
CACHE_TIMEOUT = 1
|
||||
|
||||
def should_update_cache():
|
||||
"""
|
||||
Check if the cache file exists and if it's older than the cache timeout
|
||||
"""
|
||||
if not os.path.exists(OUTPUT_FILE):
|
||||
logger.info("Cache file doesn't exist, creating it")
|
||||
return True
|
||||
|
||||
# Check file modification time
|
||||
file_mtime = datetime.fromtimestamp(os.path.getmtime(OUTPUT_FILE))
|
||||
now = datetime.now()
|
||||
|
||||
# If file is older than cache timeout, update it
|
||||
if now - file_mtime > timedelta(hours=CACHE_TIMEOUT):
|
||||
logger.info(f"Cache is older than {CACHE_TIMEOUT} hour(s), updating")
|
||||
return True
|
||||
|
||||
logger.info(f"Cache is still fresh (less than {CACHE_TIMEOUT} hour(s) old)")
|
||||
return False
|
||||
|
||||
def fetch_voting_proposals():
|
||||
"""
|
||||
Fetch proposals with "Voting" status from the OSM Wiki
|
||||
"""
|
||||
logger.info(f"Fetching voting proposals from {VOTING_PROPOSALS_URL}")
|
||||
try:
|
||||
response = requests.get(VOTING_PROPOSALS_URL)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
proposals = []
|
||||
|
||||
# Find all links in the mw-pages section
|
||||
links = soup.select('#mw-pages a')
|
||||
|
||||
for link in links:
|
||||
# Skip category links and other non-proposal links
|
||||
if 'Category:' in link.get('href', '') or 'Special:' in link.get('href', ''):
|
||||
continue
|
||||
|
||||
proposal_title = link.text.strip()
|
||||
proposal_url = 'https://wiki.openstreetmap.org' + link.get('href', '')
|
||||
|
||||
proposals.append({
|
||||
'title': proposal_title,
|
||||
'url': proposal_url,
|
||||
'status': 'Voting',
|
||||
'type': 'voting'
|
||||
})
|
||||
|
||||
logger.info(f"Found {len(proposals)} voting proposals")
|
||||
return proposals
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching voting proposals: {e}")
|
||||
return []
|
||||
|
||||
def fetch_recent_proposals():
|
||||
"""
|
||||
Fetch recently modified proposals from the OSM Wiki
|
||||
"""
|
||||
logger.info(f"Fetching recent changes from {RECENT_CHANGES_URL}")
|
||||
try:
|
||||
response = requests.get(RECENT_CHANGES_URL)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
proposals = []
|
||||
|
||||
# Find all change list lines
|
||||
change_lines = soup.select('.mw-changeslist .mw-changeslist-line')
|
||||
|
||||
for line in change_lines:
|
||||
# Get the page title
|
||||
title_element = line.select_one('.mw-changeslist-title')
|
||||
if not title_element:
|
||||
continue
|
||||
|
||||
page_title = title_element.text.strip()
|
||||
page_url = title_element.get('href', '')
|
||||
if not page_url.startswith('http'):
|
||||
page_url = f"https://wiki.openstreetmap.org{page_url}"
|
||||
|
||||
# Get the timestamp
|
||||
timestamp_element = line.select_one('.mw-changeslist-date')
|
||||
timestamp = timestamp_element.text.strip() if timestamp_element else ""
|
||||
|
||||
# Get the user who made the change
|
||||
user_element = line.select_one('.mw-userlink')
|
||||
user = user_element.text.strip() if user_element else "Unknown"
|
||||
|
||||
# Skip if it's not a proposal page
|
||||
if not page_title.startswith('Proposal:'):
|
||||
continue
|
||||
|
||||
proposals.append({
|
||||
'title': page_title,
|
||||
'url': page_url,
|
||||
'last_modified': timestamp,
|
||||
'modified_by': user,
|
||||
'type': 'recent'
|
||||
})
|
||||
|
||||
# Limit to the 10 most recent proposals
|
||||
proposals = proposals[:10]
|
||||
logger.info(f"Found {len(proposals)} recently modified proposals")
|
||||
return proposals
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching recent proposals: {e}")
|
||||
return []
|
||||
|
||||
def save_proposals(voting_proposals, recent_proposals):
|
||||
"""
|
||||
Save the proposals to a JSON file
|
||||
"""
|
||||
data = {
|
||||
'last_updated': datetime.now().isoformat(),
|
||||
'voting_proposals': voting_proposals,
|
||||
'recent_proposals': recent_proposals
|
||||
}
|
||||
|
||||
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Saved {len(voting_proposals)} voting proposals and {len(recent_proposals)} recent proposals to {OUTPUT_FILE}")
|
||||
return OUTPUT_FILE
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Fetch OSM Wiki proposals')
|
||||
parser.add_argument('--force', action='store_true', help='Force update even if cache is fresh')
|
||||
parser.add_argument('--dry-run', action='store_true', help='Print results without saving to file')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check if we should update the cache
|
||||
if args.force or should_update_cache() or args.dry_run:
|
||||
voting_proposals = fetch_voting_proposals()
|
||||
recent_proposals = fetch_recent_proposals()
|
||||
|
||||
if args.dry_run:
|
||||
logger.info(f"Found {len(voting_proposals)} voting proposals:")
|
||||
for proposal in voting_proposals:
|
||||
logger.info(f"- {proposal['title']}")
|
||||
|
||||
logger.info(f"Found {len(recent_proposals)} recent proposals:")
|
||||
for proposal in recent_proposals:
|
||||
logger.info(f"- {proposal['title']} (modified by {proposal['modified_by']} on {proposal['last_modified']})")
|
||||
else:
|
||||
output_file = save_proposals(voting_proposals, recent_proposals)
|
||||
logger.info(f"Results saved to {output_file}")
|
||||
else:
|
||||
logger.info("Using cached proposals data")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
263
wiki_compare/find_pages_unavailable_in_french.py
Executable file
263
wiki_compare/find_pages_unavailable_in_french.py
Executable file
|
@ -0,0 +1,263 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
find_pages_unavailable_in_french.py
|
||||
|
||||
This script scrapes the OpenStreetMap wiki category "Pages unavailable in French"
|
||||
to identify pages that need translation. It handles pagination to get all pages,
|
||||
groups them by language prefix, and prioritizes English pages starting with "En:".
|
||||
|
||||
Usage:
|
||||
python find_pages_unavailable_in_french.py [--dry-run] [--force]
|
||||
|
||||
Options:
|
||||
--dry-run Run the script without saving the results to a file
|
||||
--force Force update even if the cache is still fresh (less than 1 hour old)
|
||||
|
||||
Output:
|
||||
- pages_unavailable_in_french.json: JSON file with pages that need translation
|
||||
- Log messages about the scraping process and results
|
||||
"""
|
||||
|
||||
import json
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constants
|
||||
OUTPUT_FILE = "pages_unavailable_in_french.json"
|
||||
BASE_URL = "https://wiki.openstreetmap.org/wiki/Category:Pages_unavailable_in_French"
|
||||
WIKI_BASE_URL = "https://wiki.openstreetmap.org"
|
||||
CACHE_DURATION = timedelta(hours=1) # Cache duration of 1 hour
|
||||
|
||||
def is_cache_fresh():
|
||||
"""
|
||||
Check if the cache file exists and is less than CACHE_DURATION old
|
||||
|
||||
Returns:
|
||||
bool: True if cache is fresh, False otherwise
|
||||
"""
|
||||
if not os.path.exists(OUTPUT_FILE):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(OUTPUT_FILE, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
last_updated = datetime.fromisoformat(data.get('last_updated', '2000-01-01T00:00:00'))
|
||||
now = datetime.now()
|
||||
return (now - last_updated) < CACHE_DURATION
|
||||
except (IOError, json.JSONDecodeError, ValueError) as e:
|
||||
logger.error(f"Error checking cache freshness: {e}")
|
||||
return False
|
||||
|
||||
def get_page_content(url):
|
||||
"""
|
||||
Get the HTML content of a page
|
||||
|
||||
Args:
|
||||
url (str): URL to fetch
|
||||
|
||||
Returns:
|
||||
str: HTML content of the page or None if request failed
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
return response.text
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching {url}: {e}")
|
||||
return None
|
||||
|
||||
def extract_pages_from_category(html_content, current_url):
|
||||
"""
|
||||
Extract pages from the category page HTML
|
||||
|
||||
Args:
|
||||
html_content (str): HTML content of the category page
|
||||
current_url (str): URL of the current page for resolving relative links
|
||||
|
||||
Returns:
|
||||
tuple: (list of page dictionaries, next page URL or None)
|
||||
"""
|
||||
if not html_content:
|
||||
return [], None
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
pages = []
|
||||
|
||||
# Find the category content
|
||||
category_content = soup.find('div', class_='mw-category-generated')
|
||||
if not category_content:
|
||||
logger.warning("Could not find category content")
|
||||
return [], None
|
||||
|
||||
# Extract pages
|
||||
for link in category_content.find_all('a'):
|
||||
title = link.get_text()
|
||||
url = WIKI_BASE_URL + link.get('href')
|
||||
|
||||
# Extract language prefix (e.g., "En:", "De:", etc.)
|
||||
language_prefix = "Other"
|
||||
match = re.match(r'^([A-Za-z]{2}):', title)
|
||||
if match:
|
||||
language_prefix = match.group(1)
|
||||
|
||||
# Check if it's an English page
|
||||
is_english = language_prefix.lower() == "en"
|
||||
|
||||
# Set priority (English pages have higher priority)
|
||||
priority = 1 if is_english else 0
|
||||
|
||||
pages.append({
|
||||
"title": title,
|
||||
"url": url,
|
||||
"language_prefix": language_prefix,
|
||||
"is_english": is_english,
|
||||
"priority": priority
|
||||
})
|
||||
|
||||
# Find next page link
|
||||
next_page_url = None
|
||||
pagination = soup.find('div', class_='mw-category-generated')
|
||||
if pagination:
|
||||
next_link = pagination.find('a', string='next page')
|
||||
if next_link:
|
||||
next_page_url = WIKI_BASE_URL + next_link.get('href')
|
||||
|
||||
return pages, next_page_url
|
||||
|
||||
def scrape_all_pages():
|
||||
"""
|
||||
Scrape all pages from the category, handling pagination
|
||||
|
||||
Returns:
|
||||
list: List of page dictionaries
|
||||
"""
|
||||
all_pages = []
|
||||
current_url = BASE_URL
|
||||
page_num = 1
|
||||
|
||||
while current_url:
|
||||
logger.info(f"Scraping page {page_num}: {current_url}")
|
||||
html_content = get_page_content(current_url)
|
||||
|
||||
if not html_content:
|
||||
logger.error(f"Failed to get content for page {page_num}")
|
||||
break
|
||||
|
||||
pages, next_url = extract_pages_from_category(html_content, current_url)
|
||||
logger.info(f"Found {len(pages)} pages on page {page_num}")
|
||||
|
||||
all_pages.extend(pages)
|
||||
current_url = next_url
|
||||
page_num += 1
|
||||
|
||||
if not next_url:
|
||||
logger.info("No more pages to scrape")
|
||||
|
||||
logger.info(f"Total pages scraped: {len(all_pages)}")
|
||||
return all_pages
|
||||
|
||||
def group_pages_by_language(pages):
|
||||
"""
|
||||
Group pages by language prefix
|
||||
|
||||
Args:
|
||||
pages (list): List of page dictionaries
|
||||
|
||||
Returns:
|
||||
dict: Dictionary with language prefixes as keys and lists of pages as values
|
||||
"""
|
||||
grouped = {}
|
||||
|
||||
for page in pages:
|
||||
prefix = page["language_prefix"]
|
||||
if prefix not in grouped:
|
||||
grouped[prefix] = []
|
||||
grouped[prefix].append(page)
|
||||
|
||||
# Sort each group by priority (English pages first)
|
||||
for prefix in grouped:
|
||||
grouped[prefix].sort(key=lambda x: (-x["priority"], x["title"]))
|
||||
|
||||
return grouped
|
||||
|
||||
def save_results(pages, dry_run=False):
|
||||
"""
|
||||
Save the results to a JSON file
|
||||
|
||||
Args:
|
||||
pages (list): List of page dictionaries
|
||||
dry_run (bool): If True, don't actually save to file
|
||||
|
||||
Returns:
|
||||
bool: True if saving was successful or dry run, False otherwise
|
||||
"""
|
||||
if dry_run:
|
||||
logger.info("DRY RUN: Would have saved results to file")
|
||||
return True
|
||||
|
||||
# Group pages by language prefix
|
||||
grouped_pages = group_pages_by_language(pages)
|
||||
|
||||
# Prepare the data structure
|
||||
data = {
|
||||
"last_updated": datetime.now().isoformat(),
|
||||
"grouped_pages": grouped_pages,
|
||||
"all_pages": pages
|
||||
}
|
||||
|
||||
try:
|
||||
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
logger.info(f"Successfully saved {len(pages)} pages to {OUTPUT_FILE}")
|
||||
return True
|
||||
except IOError as e:
|
||||
logger.error(f"Error saving results to {OUTPUT_FILE}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main function to execute the script"""
|
||||
parser = argparse.ArgumentParser(description="Scrape pages unavailable in French from OSM wiki")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Run without saving results to file")
|
||||
parser.add_argument("--force", action="store_true", help="Force update even if cache is fresh")
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("Starting find_pages_unavailable_in_french.py")
|
||||
|
||||
# Check if cache is fresh
|
||||
if is_cache_fresh() and not args.force:
|
||||
logger.info(f"Cache is still fresh (less than {CACHE_DURATION.total_seconds()/3600} hours old)")
|
||||
logger.info(f"Use --force to update anyway")
|
||||
return
|
||||
|
||||
# Scrape pages
|
||||
pages = scrape_all_pages()
|
||||
|
||||
if not pages:
|
||||
logger.error("No pages found")
|
||||
return
|
||||
|
||||
# Save results
|
||||
success = save_results(pages, args.dry_run)
|
||||
|
||||
if success:
|
||||
logger.info("Script completed successfully")
|
||||
else:
|
||||
logger.error("Script completed with errors")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
212
wiki_compare/find_untranslated_french_pages.py
Executable file
212
wiki_compare/find_untranslated_french_pages.py
Executable file
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
find_untranslated_french_pages.py
|
||||
|
||||
This script scrapes the OSM wiki to find French pages that don't have translations
|
||||
in other languages. It caches the results and only performs the scraping
|
||||
at most once per hour.
|
||||
|
||||
Usage:
|
||||
python find_untranslated_french_pages.py [--force] [--dry-run]
|
||||
|
||||
Options:
|
||||
--force Force update even if cache is fresh
|
||||
--dry-run Print results without saving to file
|
||||
|
||||
Output:
|
||||
- untranslated_french_pages.json: JSON file containing information about French pages without translations
|
||||
"""
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import re
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Constants
|
||||
OUTPUT_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'untranslated_french_pages.json')
|
||||
CACHE_TIMEOUT = 1 # hours
|
||||
WIKI_BASE_URL = "https://wiki.openstreetmap.org"
|
||||
FRENCH_PAGES_URL = "https://wiki.openstreetmap.org/wiki/Special:AllPages?from=&to=&namespace=202&hideredirects=1&prefix=FR:"
|
||||
|
||||
def should_update_cache():
|
||||
"""
|
||||
Check if the cache file exists and if it's older than the cache timeout
|
||||
|
||||
Returns:
|
||||
bool: True if cache should be updated, False otherwise
|
||||
"""
|
||||
if not os.path.exists(OUTPUT_FILE):
|
||||
logger.info("Cache file doesn't exist, creating it")
|
||||
return True
|
||||
|
||||
# Check file modification time
|
||||
file_mtime = datetime.fromtimestamp(os.path.getmtime(OUTPUT_FILE))
|
||||
now = datetime.now()
|
||||
|
||||
# If file is older than cache timeout, update it
|
||||
if now - file_mtime > timedelta(hours=CACHE_TIMEOUT):
|
||||
logger.info(f"Cache is older than {CACHE_TIMEOUT} hour(s), updating")
|
||||
return True
|
||||
|
||||
logger.info(f"Cache is still fresh (less than {CACHE_TIMEOUT} hour(s) old)")
|
||||
return False
|
||||
|
||||
def fetch_french_pages():
|
||||
"""
|
||||
Fetch all French pages from the OSM wiki
|
||||
|
||||
Returns:
|
||||
list: List of dictionaries containing French page information
|
||||
"""
|
||||
logger.info(f"Fetching French pages from {FRENCH_PAGES_URL}")
|
||||
french_pages = []
|
||||
next_page_url = FRENCH_PAGES_URL
|
||||
|
||||
while next_page_url:
|
||||
try:
|
||||
response = requests.get(next_page_url)
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, 'html.parser')
|
||||
|
||||
# Find all links in the mw-allpages-body section
|
||||
links_container = soup.select_one('.mw-allpages-body')
|
||||
if links_container:
|
||||
links = links_container.select('li a')
|
||||
|
||||
for link in links:
|
||||
page_title = link.text.strip()
|
||||
page_url = WIKI_BASE_URL + link.get('href', '')
|
||||
|
||||
# Extract the key name (remove the FR: prefix)
|
||||
key_match = re.match(r'FR:(.*)', page_title)
|
||||
if key_match:
|
||||
key_name = key_match.group(1)
|
||||
|
||||
french_pages.append({
|
||||
'title': page_title,
|
||||
'key': key_name,
|
||||
'url': page_url,
|
||||
'has_translation': False # Will be updated later
|
||||
})
|
||||
|
||||
# Check if there's a next page
|
||||
next_link = soup.select_one('a.mw-nextlink')
|
||||
next_page_url = WIKI_BASE_URL + next_link.get('href') if next_link else None
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error fetching French pages: {e}")
|
||||
break
|
||||
|
||||
logger.info(f"Found {len(french_pages)} French pages")
|
||||
return french_pages
|
||||
|
||||
def check_translations(french_pages):
|
||||
"""
|
||||
Check if each French page has translations in other languages
|
||||
|
||||
Args:
|
||||
french_pages (list): List of dictionaries containing French page information
|
||||
|
||||
Returns:
|
||||
list: Updated list with translation information
|
||||
"""
|
||||
logger.info("Checking for translations of French pages")
|
||||
|
||||
for i, page in enumerate(french_pages):
|
||||
if i % 10 == 0: # Log progress every 10 pages
|
||||
logger.info(f"Checking page {i+1}/{len(french_pages)}: {page['title']}")
|
||||
|
||||
try:
|
||||
# Construct the English page URL by removing the FR: prefix
|
||||
en_url = page['url'].replace('/wiki/FR:', '/wiki/')
|
||||
|
||||
# Check if the English page exists
|
||||
response = requests.head(en_url)
|
||||
|
||||
# If the page returns a 200 status code, it exists
|
||||
if response.status_code == 200:
|
||||
page['has_translation'] = True
|
||||
page['en_url'] = en_url
|
||||
else:
|
||||
page['has_translation'] = False
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Error checking translation for {page['title']}: {e}")
|
||||
# Assume no translation in case of error
|
||||
page['has_translation'] = False
|
||||
|
||||
# Filter to only include pages without translations
|
||||
untranslated_pages = [page for page in french_pages if not page['has_translation']]
|
||||
logger.info(f"Found {len(untranslated_pages)} French pages without translations")
|
||||
|
||||
return untranslated_pages
|
||||
|
||||
def save_untranslated_pages(untranslated_pages):
|
||||
"""
|
||||
Save the untranslated pages to a JSON file
|
||||
|
||||
Args:
|
||||
untranslated_pages (list): List of dictionaries containing untranslated page information
|
||||
|
||||
Returns:
|
||||
str: Path to the output file
|
||||
"""
|
||||
data = {
|
||||
'last_updated': datetime.now().isoformat(),
|
||||
'untranslated_pages': untranslated_pages
|
||||
}
|
||||
|
||||
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Saved {len(untranslated_pages)} untranslated pages to {OUTPUT_FILE}")
|
||||
return OUTPUT_FILE
|
||||
|
||||
def main():
|
||||
"""Main function to execute the script"""
|
||||
parser = argparse.ArgumentParser(description="Find French OSM wiki pages without translations")
|
||||
parser.add_argument("--force", action="store_true", help="Force update even if cache is fresh")
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print results without saving to file")
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("Starting find_untranslated_french_pages.py")
|
||||
|
||||
# Check if we should update the cache
|
||||
if args.force or should_update_cache() or args.dry_run:
|
||||
# Fetch all French pages
|
||||
french_pages = fetch_french_pages()
|
||||
|
||||
# Check which ones don't have translations
|
||||
untranslated_pages = check_translations(french_pages)
|
||||
|
||||
if args.dry_run:
|
||||
logger.info(f"Found {len(untranslated_pages)} French pages without translations:")
|
||||
for page in untranslated_pages[:10]: # Show only the first 10 in dry run
|
||||
logger.info(f"- {page['title']} ({page['url']})")
|
||||
if len(untranslated_pages) > 10:
|
||||
logger.info(f"... and {len(untranslated_pages) - 10} more")
|
||||
else:
|
||||
# Save the results
|
||||
output_file = save_untranslated_pages(untranslated_pages)
|
||||
logger.info(f"Results saved to {output_file}")
|
||||
else:
|
||||
logger.info("Using cached untranslated pages data")
|
||||
|
||||
logger.info("Script completed successfully")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
104
wiki_compare/osm_fr_groups.json
Normal file
104
wiki_compare/osm_fr_groups.json
Normal file
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"last_updated": "2025-08-22T16:44:04.309688",
|
||||
"local_groups": [
|
||||
{
|
||||
"name": "Liste des groupes locaux se réunissant régulièrement",
|
||||
"url": "https://framacalc.org/osm-groupes-locaux",
|
||||
"description": "",
|
||||
"type": "local_group"
|
||||
},
|
||||
{
|
||||
"name": "Carte des groupes locaux se réunissant régulièrement",
|
||||
"url": "https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488",
|
||||
"description": "",
|
||||
"type": "local_group"
|
||||
}
|
||||
],
|
||||
"working_groups": [
|
||||
{
|
||||
"name": "Que venir faire au sein de l'association ?",
|
||||
"url": "https://forum.openstreetmap.fr/t/que-venir-faire-au-sein-de-lassociation/15454",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Inclusivité",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Inclusivité",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Technique",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Technique",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Communication externe",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Communication",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Animation de la communauté",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Animation_de_la_communauté",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Communautés locales",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Communautés_locales",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT International",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_International",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Gestion et comptabilité",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Gestion_et_comptabilité",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Soutiens",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Soutiens",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "GT Conférence SotM-FR",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#GT_Conférence_SotM-FR",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "Groupes spéciaux",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#Groupes_spéciaux",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
},
|
||||
{
|
||||
"name": "Groupes projets et thématiques",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/France/OSM-FR/Groupes_de_travail#Groupes_projets_et_thématiques",
|
||||
"description": "",
|
||||
"category": "Général",
|
||||
"type": "working_group"
|
||||
}
|
||||
],
|
||||
"umap_url": "https://umap.openstreetmap.fr/fr/map/groupes-locaux-openstreetmap_152488"
|
||||
}
|
File diff suppressed because it is too large
Load diff
494585
wiki_compare/pages_unavailable_in_french.json
Normal file
494585
wiki_compare/pages_unavailable_in_french.json
Normal file
File diff suppressed because it is too large
Load diff
18
wiki_compare/proposals.json
Normal file
18
wiki_compare/proposals.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"last_updated": "2025-08-22T17:17:53.415750",
|
||||
"voting_proposals": [
|
||||
{
|
||||
"title": "Proposal:Man made=ceremonial gate",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Proposal:Man_made%3Dceremonial_gate",
|
||||
"status": "Voting",
|
||||
"type": "voting"
|
||||
},
|
||||
{
|
||||
"title": "Proposal:Developer",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/Proposal:Developer",
|
||||
"status": "Voting",
|
||||
"type": "voting"
|
||||
}
|
||||
],
|
||||
"recent_proposals": []
|
||||
}
|
17
wiki_compare/suspicious_deletions.json
Normal file
17
wiki_compare/suspicious_deletions.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"last_updated": "2025-08-22T16:37:41.133091",
|
||||
"deletions": [
|
||||
{
|
||||
"page_title": "FR:Mapa en català",
|
||||
"page_url": "https://wiki.openstreetmap.org/wiki/FR:Mapa_en_catal%C3%A0",
|
||||
"diff_url": "",
|
||||
"history_url": "https://wiki.openstreetmap.org/w/index.php?title=FR:Mapa en català&action=history",
|
||||
"deletion_size": 616,
|
||||
"total_chars": 6106,
|
||||
"deletion_percentage": 10.09,
|
||||
"timestamp": "",
|
||||
"user": "LySioS",
|
||||
"comment": "(màj méthode et BAL pour mairie)"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,82 +1,202 @@
|
|||
[
|
||||
{
|
||||
"key": "building",
|
||||
"count": 656287377
|
||||
"count": 656385865
|
||||
},
|
||||
{
|
||||
"key": "source",
|
||||
"count": 299623433
|
||||
"count": 299647859
|
||||
},
|
||||
{
|
||||
"key": "highway",
|
||||
"count": 280087725
|
||||
"count": 280148158
|
||||
},
|
||||
{
|
||||
"key": "addr:housenumber",
|
||||
"count": 171973287
|
||||
"count": 171945679
|
||||
},
|
||||
{
|
||||
"key": "addr:street",
|
||||
"count": 160834345
|
||||
"count": 160796997
|
||||
},
|
||||
{
|
||||
"key": "addr:city",
|
||||
"count": 123283625
|
||||
"count": 123238768
|
||||
},
|
||||
{
|
||||
"key": "name",
|
||||
"count": 109176151
|
||||
"count": 109198278
|
||||
},
|
||||
{
|
||||
"key": "addr:postcode",
|
||||
"count": 106943837
|
||||
"count": 106896062
|
||||
},
|
||||
{
|
||||
"key": "natural",
|
||||
"count": 84435807
|
||||
"count": 84462003
|
||||
},
|
||||
{
|
||||
"key": "surface",
|
||||
"count": 72048796
|
||||
"count": 72076206
|
||||
},
|
||||
{
|
||||
"key": "addr:country",
|
||||
"count": 50511041
|
||||
"count": 50514250
|
||||
},
|
||||
{
|
||||
"key": "landuse",
|
||||
"count": 48098130
|
||||
"count": 48109623
|
||||
},
|
||||
{
|
||||
"key": "power",
|
||||
"count": 44639130
|
||||
"count": 44663063
|
||||
},
|
||||
{
|
||||
"key": "waterway",
|
||||
"count": 37153506
|
||||
"count": 37159863
|
||||
},
|
||||
{
|
||||
"key": "building:levels",
|
||||
"count": 36426521
|
||||
"count": 36434874
|
||||
},
|
||||
{
|
||||
"key": "amenity",
|
||||
"count": 30874207
|
||||
"count": 30885582
|
||||
},
|
||||
{
|
||||
"key": "barrier",
|
||||
"count": 30063102
|
||||
"count": 30072729
|
||||
},
|
||||
{
|
||||
"key": "source:date",
|
||||
"count": 29107386
|
||||
"count": 29105774
|
||||
},
|
||||
{
|
||||
"key": "service",
|
||||
"count": 28326346
|
||||
"count": 28333572
|
||||
},
|
||||
{
|
||||
"key": "addr:state",
|
||||
"count": 25331031
|
||||
"count": 25334152
|
||||
},
|
||||
{
|
||||
"key": "access",
|
||||
"count": 24181609
|
||||
},
|
||||
{
|
||||
"key": "oneway",
|
||||
"count": 24104463
|
||||
},
|
||||
{
|
||||
"key": "height",
|
||||
"count": 22722628
|
||||
},
|
||||
{
|
||||
"key": "ref",
|
||||
"count": 21319224
|
||||
},
|
||||
{
|
||||
"key": "maxspeed",
|
||||
"count": 20590670
|
||||
},
|
||||
{
|
||||
"key": "lanes",
|
||||
"count": 18429768
|
||||
},
|
||||
{
|
||||
"key": "start_date",
|
||||
"count": 17794337
|
||||
},
|
||||
{
|
||||
"key": "addr:district",
|
||||
"count": 16216409
|
||||
},
|
||||
{
|
||||
"key": "layer",
|
||||
"count": 14666751
|
||||
},
|
||||
{
|
||||
"key": "type",
|
||||
"count": 13753788
|
||||
},
|
||||
{
|
||||
"key": "operator",
|
||||
"count": 13510261
|
||||
},
|
||||
{
|
||||
"key": "lit",
|
||||
"count": 13412110
|
||||
},
|
||||
{
|
||||
"key": "wall",
|
||||
"count": 12771580
|
||||
},
|
||||
{
|
||||
"key": "tiger:cfcc",
|
||||
"count": 12592955
|
||||
},
|
||||
{
|
||||
"key": "crossing",
|
||||
"count": 12512388
|
||||
},
|
||||
{
|
||||
"key": "tiger:county",
|
||||
"count": 12488330
|
||||
},
|
||||
{
|
||||
"key": "source:addr",
|
||||
"count": 12377162
|
||||
},
|
||||
{
|
||||
"key": "footway",
|
||||
"count": 11461964
|
||||
},
|
||||
{
|
||||
"key": "ref:bag",
|
||||
"count": 11341389
|
||||
},
|
||||
{
|
||||
"key": "addr:place",
|
||||
"count": 11132132
|
||||
},
|
||||
{
|
||||
"key": "tiger:reviewed",
|
||||
"count": 10788557
|
||||
},
|
||||
{
|
||||
"key": "leisure",
|
||||
"count": 10656018
|
||||
},
|
||||
{
|
||||
"key": "addr:suburb",
|
||||
"count": 10308389
|
||||
},
|
||||
{
|
||||
"key": "ele",
|
||||
"count": 10261636
|
||||
},
|
||||
{
|
||||
"key": "tracktype",
|
||||
"count": 10147836
|
||||
},
|
||||
{
|
||||
"key": "addr:neighbourhood",
|
||||
"count": 10130674
|
||||
},
|
||||
{
|
||||
"key": "addr:hamlet",
|
||||
"count": 9972893
|
||||
},
|
||||
{
|
||||
"key": "addr:province",
|
||||
"count": 9653154
|
||||
},
|
||||
{
|
||||
"key": "leaf_type",
|
||||
"count": 9511262
|
||||
},
|
||||
{
|
||||
"key": "addr:full",
|
||||
"count": 9434493
|
||||
}
|
||||
]
|
947
wiki_compare/untranslated_french_pages.json
Normal file
947
wiki_compare/untranslated_french_pages.json
Normal file
|
@ -0,0 +1,947 @@
|
|||
{
|
||||
"last_updated": "2025-08-22T15:22:37.234265",
|
||||
"untranslated_pages": [
|
||||
{
|
||||
"title": "FR:2017 Ouragans Irma et Maria",
|
||||
"key": "2017 Ouragans Irma et Maria",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:2017_Ouragans_Irma_et_Maria",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:ARTM - Autorité régionale de transport métropolitain",
|
||||
"key": "ARTM - Autorité régionale de transport métropolitain",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:ARTM_-_Autorit%C3%A9_r%C3%A9gionale_de_transport_m%C3%A9tropolitain",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Accessibilité",
|
||||
"key": "Accessibilité",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Accessibilit%C3%A9",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Accompagner les débutants",
|
||||
"key": "Accompagner les débutants",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Accompagner_les_d%C3%A9butants",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Adresses",
|
||||
"key": "Adresses",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Adresses",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Adresses/Brouillon",
|
||||
"key": "Adresses/Brouillon",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Adresses/Brouillon",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Aide",
|
||||
"key": "Aide",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Aide",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Aide du wiki",
|
||||
"key": "Aide du wiki",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Aide_du_wiki",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Aires d'accueil des gens du voyage",
|
||||
"key": "Aires d'accueil des gens du voyage",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Aires_d%27accueil_des_gens_du_voyage",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Algérie",
|
||||
"key": "Algérie",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Alg%C3%A9rie",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Animaux",
|
||||
"key": "Animaux",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Animaux",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Annulation des modifications",
|
||||
"key": "Annulation des modifications",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Annulation_des_modifications",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Annulation des suppressions de données",
|
||||
"key": "Annulation des suppressions de données",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Annulation_des_suppressions_de_donn%C3%A9es",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Anomalies",
|
||||
"key": "Anomalies",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Anomalies",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Antananarivo",
|
||||
"key": "Antananarivo",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Antananarivo",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Antsiranana",
|
||||
"key": "Antsiranana",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Antsiranana",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Aperçu des composants",
|
||||
"key": "Aperçu des composants",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Aper%C3%A7u_des_composants",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Argumentaires",
|
||||
"key": "Argumentaires",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Argumentaires",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Assurance qualité",
|
||||
"key": "Assurance qualité",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Assurance_qualit%C3%A9",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Attributs",
|
||||
"key": "Attributs",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Attributs",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Attributs OSM Humanitaire",
|
||||
"key": "Attributs OSM Humanitaire",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Attributs_OSM_Humanitaire",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Attributs suppressibles",
|
||||
"key": "Attributs suppressibles",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Attributs_suppressibles",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Augustind/testpage",
|
||||
"key": "Augustind/testpage",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Augustind/testpage",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Autres cartes en ligne",
|
||||
"key": "Autres cartes en ligne",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Autres_cartes_en_ligne",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Avant et arrière, gauche et droite",
|
||||
"key": "Avant et arrière, gauche et droite",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Avant_et_arri%C3%A8re,_gauche_et_droite",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Baignade",
|
||||
"key": "Baignade",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Baignade",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Balisage maritime/Catégories pour les objets",
|
||||
"key": "Balisage maritime/Catégories pour les objets",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Balisage_maritime/Cat%C3%A9gories_pour_les_objets",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Balisage maritime/Etiquettes et valeurs",
|
||||
"key": "Balisage maritime/Etiquettes et valeurs",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Balisage_maritime/Etiquettes_et_valeurs",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Balisage maritime/Mouillages",
|
||||
"key": "Balisage maritime/Mouillages",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Balisage_maritime/Mouillages",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Balisage maritime/Objets de balisage",
|
||||
"key": "Balisage maritime/Objets de balisage",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Balisage_maritime/Objets_de_balisage",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Balisage maritime/Schéma de balisage",
|
||||
"key": "Balisage maritime/Schéma de balisage",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Balisage_maritime/Sch%C3%A9ma_de_balisage",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Balisage maritime/Utilisation des objets",
|
||||
"key": "Balisage maritime/Utilisation des objets",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Balisage_maritime/Utilisation_des_objets",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Barrières",
|
||||
"key": "Barrières",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Barri%C3%A8res",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:BeCikloXmlFond",
|
||||
"key": "BeCikloXmlFond",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:BeCikloXmlFond",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:BeCikloXmlInfos",
|
||||
"key": "BeCikloXmlInfos",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:BeCikloXmlInfos",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:BeCikloXmlPistes",
|
||||
"key": "BeCikloXmlPistes",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:BeCikloXmlPistes",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Beginners Guide 1.3.3",
|
||||
"key": "Beginners Guide 1.3.3",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Beginners_Guide_1.3.3",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:BelvederesLez",
|
||||
"key": "BelvederesLez",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:BelvederesLez",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bibliographie",
|
||||
"key": "Bibliographie",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Bibliographie",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bienvenue aux wikipediens",
|
||||
"key": "Bienvenue aux wikipediens",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Bienvenue_aux_wikipediens",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bing Cartes",
|
||||
"key": "Bing Cartes",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Bing_Cartes",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Blogues d'OSM",
|
||||
"key": "Blogues d'OSM",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Blogues_d%27OSM",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bonnes pratiques",
|
||||
"key": "Bonnes pratiques",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Bonnes_pratiques",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bons commentaires de groupe de modifications",
|
||||
"key": "Bons commentaires de groupe de modifications",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Bons_commentaires_de_groupe_de_modifications",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bookmarklet OSM-Mapillary",
|
||||
"key": "Bookmarklet OSM-Mapillary",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Bookmarklet_OSM-Mapillary",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Breton, noms geographiques et toponymes",
|
||||
"key": "Breton, noms geographiques et toponymes",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Breton,_noms_geographiques_et_toponymes",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Breton, rendu br",
|
||||
"key": "Breton, rendu br",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Breton,_rendu_br",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bâtiments",
|
||||
"key": "Bâtiments",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:B%C3%A2timents",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Bâtiments 3D simples",
|
||||
"key": "Bâtiments 3D simples",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:B%C3%A2timents_3D_simples",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cadastre espagnol",
|
||||
"key": "Cadastre espagnol",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cadastre_espagnol",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cadastre espagnol/2011-2016",
|
||||
"key": "Cadastre espagnol/2011-2016",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cadastre_espagnol/2011-2016",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cadastre français/archives",
|
||||
"key": "Cadastre français/archives",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cadastre_fran%C3%A7ais/archives",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Calcul d'itinéraires",
|
||||
"key": "Calcul d'itinéraires",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Calcul_d%27itin%C3%A9raires",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Calque Données de carte",
|
||||
"key": "Calque Données de carte",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Calque_Donn%C3%A9es_de_carte",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Canada/Signalisation routière/Québec",
|
||||
"key": "Canada/Signalisation routière/Québec",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Canada/Signalisation_routi%C3%A8re/Qu%C3%A9bec",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Canada/Signalisation routière/Québec/Danger",
|
||||
"key": "Canada/Signalisation routière/Québec/Danger",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Canada/Signalisation_routi%C3%A8re/Qu%C3%A9bec/Danger",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Canada/Signalisation routière/Québec/Prescription",
|
||||
"key": "Canada/Signalisation routière/Québec/Prescription",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Canada/Signalisation_routi%C3%A8re/Qu%C3%A9bec/Prescription",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Canaux de contact",
|
||||
"key": "Canaux de contact",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Canaux_de_contact",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:CarteInnov",
|
||||
"key": "CarteInnov",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:CarteInnov",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartes",
|
||||
"key": "Cartes",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartes",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:CartoSatCamp",
|
||||
"key": "CartoSatCamp",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:CartoSatCamp",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartographie",
|
||||
"key": "Cartographie",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartographie",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartographie d'après des photos",
|
||||
"key": "Cartographie d'après des photos",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartographie_d%27apr%C3%A8s_des_photos",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartographie des camps de réfugiés",
|
||||
"key": "Cartographie des camps de réfugiés",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartographie_des_camps_de_r%C3%A9fugi%C3%A9s",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartographie des routes en France",
|
||||
"key": "Cartographie des routes en France",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartographie_des_routes_en_France",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartographier depuis son fauteuil",
|
||||
"key": "Cartographier depuis son fauteuil",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartographier_depuis_son_fauteuil",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartoparties",
|
||||
"key": "Cartoparties",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartoparties",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cartothèque",
|
||||
"key": "Cartothèque",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cartoth%C3%A8que",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Catalogue des erreurs",
|
||||
"key": "Catalogue des erreurs",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Catalogue_des_erreurs",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Chemin",
|
||||
"key": "Chemin",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Chemin",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cheminements piétons",
|
||||
"key": "Cheminements piétons",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cheminements_pi%C3%A9tons",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Chemins de fer",
|
||||
"key": "Chemins de fer",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Chemins_de_fer",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Chemins non connectés",
|
||||
"key": "Chemins non connectés",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Chemins_non_connect%C3%A9s",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Choisir votre méthode de collecte de données",
|
||||
"key": "Choisir votre méthode de collecte de données",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Choisir_votre_m%C3%A9thode_de_collecte_de_donn%C3%A9es",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Chute d'eau",
|
||||
"key": "Chute d'eau",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Chute_d%27eau",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Circonscriptions législatives en France",
|
||||
"key": "Circonscriptions législatives en France",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Circonscriptions_l%C3%A9gislatives_en_France",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Code de conduite des modifications automatisées",
|
||||
"key": "Code de conduite des modifications automatisées",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Code_de_conduite_des_modifications_automatis%C3%A9es",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:ComcomMaker",
|
||||
"key": "ComcomMaker",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:ComcomMaker",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comment cartographier les besoins des personnes handicapées",
|
||||
"key": "Comment cartographier les besoins des personnes handicapées",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comment_cartographier_les_besoins_des_personnes_handicap%C3%A9es",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comment cartographier un...",
|
||||
"key": "Comment cartographier un...",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comment_cartographier_un...",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comment cartographier un (santé)",
|
||||
"key": "Comment cartographier un (santé)",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comment_cartographier_un_(sant%C3%A9)",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comment contribuer",
|
||||
"key": "Comment contribuer",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comment_contribuer",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comores",
|
||||
"key": "Comores",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comores",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comparaison des concepts de cycle de vie",
|
||||
"key": "Comparaison des concepts de cycle de vie",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comparaison_des_concepts_de_cycle_de_vie",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comparaison des éditeurs",
|
||||
"key": "Comparaison des éditeurs",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comparaison_des_%C3%A9diteurs",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Complète Tes Commerces/Avancé",
|
||||
"key": "Complète Tes Commerces/Avancé",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Compl%C3%A8te_Tes_Commerces/Avanc%C3%A9",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Complète Tes Commerces/Debutant",
|
||||
"key": "Complète Tes Commerces/Debutant",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Compl%C3%A8te_Tes_Commerces/Debutant",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Complète Tes Commerces/Débutant",
|
||||
"key": "Complète Tes Commerces/Débutant",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Compl%C3%A8te_Tes_Commerces/D%C3%A9butant",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Comptes services OpenStreetMap France",
|
||||
"key": "Comptes services OpenStreetMap France",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Comptes_services_OpenStreetMap_France",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Congo-Kinshasa/Réponse OSM 2020 à l'épidémie Ebola",
|
||||
"key": "Congo-Kinshasa/Réponse OSM 2020 à l'épidémie Ebola",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Congo-Kinshasa/R%C3%A9ponse_OSM_2020_%C3%A0_l%27%C3%A9pid%C3%A9mie_Ebola",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Conserver l'historique",
|
||||
"key": "Conserver l'historique",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Conserver_l%27historique",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Contacts pour la presse",
|
||||
"key": "Contacts pour la presse",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Contacts_pour_la_presse",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Contribuer aux données cartographiques",
|
||||
"key": "Contribuer aux données cartographiques",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Contribuer_aux_donn%C3%A9es_cartographiques",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Conversion des traces GPS",
|
||||
"key": "Conversion des traces GPS",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Conversion_des_traces_GPS",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Corine Land Cover/Nomenclature",
|
||||
"key": "Corine Land Cover/Nomenclature",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Corine_Land_Cover/Nomenclature",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Corine Land Cover/Opérations post-import",
|
||||
"key": "Corine Land Cover/Opérations post-import",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Corine_Land_Cover/Op%C3%A9rations_post-import",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cours d'eau",
|
||||
"key": "Cours d'eau",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cours_d%27eau",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Créer un attribut qui manque",
|
||||
"key": "Créer un attribut qui manque",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cr%C3%A9er_un_attribut_qui_manque",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Cyclisme tout terrain",
|
||||
"key": "Cyclisme tout terrain",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Cyclisme_tout_terrain",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Câble aérien",
|
||||
"key": "Câble aérien",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:C%C3%A2ble_a%C3%A9rien",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:DRC Tagging Guidelines",
|
||||
"key": "DRC Tagging Guidelines",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:DRC_Tagging_Guidelines",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Datex",
|
||||
"key": "Datex",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Datex",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Debian/Bullseye/Installation",
|
||||
"key": "Debian/Bullseye/Installation",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Debian/Bullseye/Installation",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Demander aux contributeurs d'accepter l'ODbL",
|
||||
"key": "Demander aux contributeurs d'accepter l'ODbL",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Demander_aux_contributeurs_d%27accepter_l%27ODbL",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Directives du wiki",
|
||||
"key": "Directives du wiki",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Directives_du_wiki",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Débat Traduction",
|
||||
"key": "Débat Traduction",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:D%C3%A9bat_Traduction",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Découpage administratif de la Tunisie",
|
||||
"key": "Découpage administratif de la Tunisie",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:D%C3%A9coupage_administratif_de_la_Tunisie",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Déployer sa propre Slippy Map",
|
||||
"key": "Déployer sa propre Slippy Map",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:D%C3%A9ployer_sa_propre_Slippy_Map",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Développement 3D",
|
||||
"key": "Développement 3D",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:D%C3%A9veloppement_3D",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Développer",
|
||||
"key": "Développer",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:D%C3%A9velopper",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:EXO - Autorisation import GTFS",
|
||||
"key": "EXO - Autorisation import GTFS",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:EXO_-_Autorisation_import_GTFS",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:EXO - Réseau de transport métropolitain (autobus)",
|
||||
"key": "EXO - Réseau de transport métropolitain (autobus)",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:EXO_-_R%C3%A9seau_de_transport_m%C3%A9tropolitain_(autobus)",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:EXO - Réseau de transport métropolitain (train)",
|
||||
"key": "EXO - Réseau de transport métropolitain (train)",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:EXO_-_R%C3%A9seau_de_transport_m%C3%A9tropolitain_(train)",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:English CarteInnov",
|
||||
"key": "English CarteInnov",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:English_CarteInnov",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Enregistrement de traces GPS",
|
||||
"key": "Enregistrement de traces GPS",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Enregistrement_de_traces_GPS",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Environnement marin",
|
||||
"key": "Environnement marin",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Environnement_marin",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Escalade",
|
||||
"key": "Escalade",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Escalade",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Espaces de noms",
|
||||
"key": "Espaces de noms",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Espaces_de_noms",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Espagne",
|
||||
"key": "Espagne",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Espagne",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Exactitude",
|
||||
"key": "Exactitude",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Exactitude",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Exemples de chemins",
|
||||
"key": "Exemples de chemins",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Exemples_de_chemins",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Flash Map Mob",
|
||||
"key": "Flash Map Mob",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Flash_Map_Mob",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Fondation",
|
||||
"key": "Fondation",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Fondation",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Fondation/Chapitres locaux",
|
||||
"key": "Fondation/Chapitres locaux",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Fondation/Chapitres_locaux",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Format PBF",
|
||||
"key": "Format PBF",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Format_PBF",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Format de fichier JOSM",
|
||||
"key": "Format de fichier JOSM",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Format_de_fichier_JOSM",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Formations",
|
||||
"key": "Formations",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Formations",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Fortunes",
|
||||
"key": "Fortunes",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:Fortunes",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:Forêt",
|
||||
"key": "Forêt",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:For%C3%AAt",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Actualisation en temps réel",
|
||||
"key": "France/Actualisation en temps réel",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Actualisation_en_temps_r%C3%A9el",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Base Adresses Nationale Ouverte (BANO)",
|
||||
"key": "France/Base Adresses Nationale Ouverte (BANO)",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Base_Adresses_Nationale_Ouverte_(BANO)",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Base Adresses Nationale Ouverte (BANO)/Liste noire",
|
||||
"key": "France/Base Adresses Nationale Ouverte (BANO)/Liste noire",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Base_Adresses_Nationale_Ouverte_(BANO)/Liste_noire",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Base Adresses Nationale Ouverte (BANO)/MapCraft: communes sans aucune rue nommée",
|
||||
"key": "France/Base Adresses Nationale Ouverte (BANO)/MapCraft: communes sans aucune rue nommée",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Base_Adresses_Nationale_Ouverte_(BANO)/MapCraft:_communes_sans_aucune_rue_nomm%C3%A9e",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Base Adresses Nationale Ouverte (BANO)/OpenData",
|
||||
"key": "France/Base Adresses Nationale Ouverte (BANO)/OpenData",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Base_Adresses_Nationale_Ouverte_(BANO)/OpenData",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Base Adresses Nationale Ouverte (BANO)/Technique",
|
||||
"key": "France/Base Adresses Nationale Ouverte (BANO)/Technique",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Base_Adresses_Nationale_Ouverte_(BANO)/Technique",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Cadastre",
|
||||
"key": "France/Cadastre",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Cadastre",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/DFCI et DECI",
|
||||
"key": "France/DFCI et DECI",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/DFCI_et_DECI",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Intégration des adresses avec les outils BANO",
|
||||
"key": "France/Intégration des adresses avec les outils BANO",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Int%C3%A9gration_des_adresses_avec_les_outils_BANO",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Intégration des adresses depuis la BAN",
|
||||
"key": "France/Intégration des adresses depuis la BAN",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Int%C3%A9gration_des_adresses_depuis_la_BAN",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Intégration des adresses depuis la BAN/BAN.json",
|
||||
"key": "France/Intégration des adresses depuis la BAN/BAN.json",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Int%C3%A9gration_des_adresses_depuis_la_BAN/BAN.json",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map",
|
||||
"key": "France/Map",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/COM",
|
||||
"key": "France/Map/COM",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/COM",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/DOM",
|
||||
"key": "France/Map/DOM",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/DOM",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/France",
|
||||
"key": "France/Map/France",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/France",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/France métropolitaine",
|
||||
"key": "France/Map/France métropolitaine",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/France_m%C3%A9tropolitaine",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Guadeloupe",
|
||||
"key": "France/Map/Guadeloupe",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Guadeloupe",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Guyane",
|
||||
"key": "France/Map/Guyane",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Guyane",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/La Réunion",
|
||||
"key": "France/Map/La Réunion",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/La_R%C3%A9union",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Martinique",
|
||||
"key": "France/Map/Martinique",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Martinique",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Mayotte",
|
||||
"key": "France/Map/Mayotte",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Mayotte",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Nouvelle-Calédonie",
|
||||
"key": "France/Map/Nouvelle-Calédonie",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Nouvelle-Cal%C3%A9donie",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Outre-Mer",
|
||||
"key": "France/Map/Outre-Mer",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Outre-Mer",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Polynésie française",
|
||||
"key": "France/Map/Polynésie française",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Polyn%C3%A9sie_fran%C3%A7aise",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Saint-Barthélemy",
|
||||
"key": "France/Map/Saint-Barthélemy",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Saint-Barth%C3%A9lemy",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Saint-Martin",
|
||||
"key": "France/Map/Saint-Martin",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Saint-Martin",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Saint-Pierre-et-Miquelon",
|
||||
"key": "France/Map/Saint-Pierre-et-Miquelon",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Saint-Pierre-et-Miquelon",
|
||||
"has_translation": false
|
||||
},
|
||||
{
|
||||
"title": "FR:France/Map/Terres australes et antarctiques françaises",
|
||||
"key": "France/Map/Terres australes et antarctiques françaises",
|
||||
"url": "https://wiki.openstreetmap.org/wiki/FR:France/Map/Terres_australes_et_antarctiques_fran%C3%A7aises",
|
||||
"has_translation": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -43,7 +43,7 @@ TOP_KEYS_FILE = "top_keys.json"
|
|||
WIKI_PAGES_CSV = "wiki_pages.csv"
|
||||
OUTDATED_PAGES_FILE = "outdated_pages.json"
|
||||
# Number of wiki pages to examine
|
||||
NUM_WIKI_PAGES = 20
|
||||
NUM_WIKI_PAGES = 50
|
||||
|
||||
def fetch_top_keys(limit=NUM_WIKI_PAGES):
|
||||
"""
|
||||
|
@ -144,10 +144,14 @@ def fetch_wiki_page(key, language='en'):
|
|||
# Extract section titles
|
||||
section_titles = []
|
||||
for section_elem in section_elements:
|
||||
# Skip sections that are part of the table of contents or navigation
|
||||
# Skip sections that are part of the table of contents, navigation, or DescriptionBox
|
||||
if section_elem.parent and section_elem.parent.get('id') in ['toc', 'mw-navigation']:
|
||||
continue
|
||||
|
||||
# Skip sections that are inside a table with class DescriptionBox
|
||||
if section_elem.find_parent('table', class_='DescriptionBox'):
|
||||
continue
|
||||
|
||||
# Get the text of the section title, removing any edit links
|
||||
for edit_link in section_elem.select('.mw-editsection'):
|
||||
edit_link.extract()
|
||||
|
@ -167,6 +171,10 @@ def fetch_wiki_page(key, language='en'):
|
|||
for script in content.select('script, style'):
|
||||
script.extract()
|
||||
|
||||
# Remove .languages elements
|
||||
for languages_elem in content.select('.languages'):
|
||||
languages_elem.extract()
|
||||
|
||||
# Get text and count words
|
||||
text = content.get_text(separator=' ', strip=True)
|
||||
word_count = len(text.split())
|
||||
|
@ -214,12 +222,19 @@ def fetch_wiki_page(key, language='en'):
|
|||
'src': src,
|
||||
'alt': alt_text
|
||||
})
|
||||
|
||||
# Extract categories
|
||||
categories = []
|
||||
category_links = soup.select('#mw-normal-catlinks li a')
|
||||
for cat_link in category_links:
|
||||
categories.append(cat_link.get_text(strip=True))
|
||||
else:
|
||||
word_count = 0
|
||||
link_count = 0
|
||||
link_details = []
|
||||
media_count = 0
|
||||
media_details = []
|
||||
categories = []
|
||||
|
||||
return {
|
||||
'key': key,
|
||||
|
@ -232,7 +247,8 @@ def fetch_wiki_page(key, language='en'):
|
|||
'link_count': link_count,
|
||||
'link_details': link_details,
|
||||
'media_count': media_count,
|
||||
'media_details': media_details
|
||||
'media_details': media_details,
|
||||
'categories': categories
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
@ -300,7 +316,8 @@ def analyze_wiki_pages(pages):
|
|||
'priority': missing_staleness_score, # Use staleness score as priority
|
||||
'section_comparison': None, # No comparison possible
|
||||
'link_comparison': None, # No comparison possible
|
||||
'media_comparison': None # No comparison possible
|
||||
'media_comparison': None, # No comparison possible
|
||||
'category_comparison': None # No comparison possible
|
||||
})
|
||||
continue
|
||||
|
||||
|
@ -430,6 +447,32 @@ def analyze_wiki_pages(pages):
|
|||
if not media['alt'] or media['alt'].lower() not in fr_media:
|
||||
media_comparison['fr_only'].append(media)
|
||||
|
||||
# Compare categories between English and French pages
|
||||
category_comparison = {
|
||||
'en_only': [],
|
||||
'fr_only': [],
|
||||
'common': []
|
||||
}
|
||||
|
||||
# Extract categories for comparison (case insensitive)
|
||||
en_categories = [cat.lower() for cat in en_page.get('categories', [])]
|
||||
fr_categories = [cat.lower() for cat in fr_page.get('categories', [])]
|
||||
|
||||
# Find categories only in English
|
||||
for cat in en_page.get('categories', []):
|
||||
if cat.lower() not in fr_categories:
|
||||
category_comparison['en_only'].append(cat)
|
||||
|
||||
# Find categories only in French
|
||||
for cat in fr_page.get('categories', []):
|
||||
if cat.lower() not in en_categories:
|
||||
category_comparison['fr_only'].append(cat)
|
||||
|
||||
# Find common categories
|
||||
for cat in en_page.get('categories', []):
|
||||
if cat.lower() in fr_categories:
|
||||
category_comparison['common'].append(cat)
|
||||
|
||||
if date_diff > 30 or word_diff > 200 or section_diff > 2 or link_diff > 20 or fr_page['word_count'] < en_page['word_count'] * 0.7:
|
||||
reason = []
|
||||
if date_diff > 30:
|
||||
|
@ -459,7 +502,8 @@ def analyze_wiki_pages(pages):
|
|||
'priority': staleness_score, # Use staleness score as priority
|
||||
'section_comparison': section_comparison,
|
||||
'link_comparison': link_comparison,
|
||||
'media_comparison': media_comparison
|
||||
'media_comparison': media_comparison,
|
||||
'category_comparison': category_comparison
|
||||
})
|
||||
|
||||
# Sort by priority (descending)
|
||||
|
|
|
@ -1,40 +1,90 @@
|
|||
key,language,url,last_modified,sections,word_count,link_count,media_count,staleness_score
|
||||
building,en,https://wiki.openstreetmap.org/wiki/Key:building,2025-06-10,31,3873,712,158,8.91
|
||||
building,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building,2025-05-22,25,3280,629,155,8.91
|
||||
source,en,https://wiki.openstreetmap.org/wiki/Key:source,2025-08-12,27,2851,399,42,113.06
|
||||
source,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source,2024-02-07,23,2692,315,35,113.06
|
||||
highway,en,https://wiki.openstreetmap.org/wiki/Key:highway,2025-04-10,30,4225,865,314,20.35
|
||||
highway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:highway,2025-01-05,30,4240,780,313,20.35
|
||||
addr:housenumber,en,https://wiki.openstreetmap.org/wiki/Key:addr:housenumber,2025-07-24,11,429,182,20,32.04
|
||||
addr:housenumber,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:housenumber,2025-03-26,15,1754,236,78,32.04
|
||||
addr:street,en,https://wiki.openstreetmap.org/wiki/Key:addr:street,2024-10-29,12,701,186,16,36.07
|
||||
addr:street,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:street,2025-03-26,15,1754,236,78,36.07
|
||||
addr:city,en,https://wiki.openstreetmap.org/wiki/Key:addr:city,2025-07-29,15,901,190,17,29.96
|
||||
addr:city,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:city,2025-03-26,15,1754,236,78,29.96
|
||||
name,en,https://wiki.openstreetmap.org/wiki/Key:name,2025-07-25,17,2295,366,82,42.39
|
||||
name,fr,https://wiki.openstreetmap.org/wiki/FR:Key:name,2025-01-16,21,1819,272,60,42.39
|
||||
addr:postcode,en,https://wiki.openstreetmap.org/wiki/Key:addr:postcode,2024-10-29,14,481,168,11,37.14
|
||||
addr:postcode,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:postcode,2025-03-26,15,1754,236,78,37.14
|
||||
natural,en,https://wiki.openstreetmap.org/wiki/Key:natural,2025-07-17,17,2169,620,189,22.06
|
||||
natural,fr,https://wiki.openstreetmap.org/wiki/FR:Key:natural,2025-04-21,13,1598,540,174,22.06
|
||||
surface,en,https://wiki.openstreetmap.org/wiki/Key:surface,2025-06-29,24,3574,676,238,252.64
|
||||
surface,fr,https://wiki.openstreetmap.org/wiki/FR:Key:surface,2022-02-22,13,2686,546,232,252.64
|
||||
addr:country,en,https://wiki.openstreetmap.org/wiki/Key:addr:country,2024-12-01,9,283,150,11,22.96
|
||||
addr:country,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:country,2025-03-25,8,286,150,11,22.96
|
||||
landuse,en,https://wiki.openstreetmap.org/wiki/Key:landuse,2025-03-01,17,2170,531,168,39.41
|
||||
landuse,fr,https://wiki.openstreetmap.org/wiki/FR:Key:landuse,2024-08-20,19,2152,503,182,39.41
|
||||
power,en,https://wiki.openstreetmap.org/wiki/Key:power,2025-02-28,20,740,212,21,124.89
|
||||
power,fr,https://wiki.openstreetmap.org/wiki/FR:Key:power,2023-06-27,14,489,190,25,124.89
|
||||
waterway,en,https://wiki.openstreetmap.org/wiki/Key:waterway,2025-03-10,21,1929,450,118,77.94
|
||||
waterway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:waterway,2024-03-08,18,1390,357,113,77.94
|
||||
building:levels,en,https://wiki.openstreetmap.org/wiki/Key:building:levels,2025-08-13,16,1450,289,25,76.11
|
||||
building:levels,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building:levels,2024-08-01,15,1556,287,26,76.11
|
||||
amenity,en,https://wiki.openstreetmap.org/wiki/Key:amenity,2025-03-16,29,3139,999,504,128.43
|
||||
amenity,fr,https://wiki.openstreetmap.org/wiki/FR:Key:amenity,2023-07-19,22,2245,885,487,128.43
|
||||
barrier,en,https://wiki.openstreetmap.org/wiki/Key:barrier,2025-04-15,17,2236,528,173,207.98
|
||||
barrier,fr,https://wiki.openstreetmap.org/wiki/FR:Key:barrier,2022-08-16,15,641,188,18,207.98
|
||||
source:date,en,https://wiki.openstreetmap.org/wiki/Key:source:date,2023-04-01,11,494,160,10,22.47
|
||||
source:date,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source:date,2023-07-21,10,518,160,11,22.47
|
||||
service,en,https://wiki.openstreetmap.org/wiki/Key:service,2025-03-16,22,1535,303,17,83.79
|
||||
service,fr,https://wiki.openstreetmap.org/wiki/FR:Key:service,2024-03-04,11,542,185,10,83.79
|
||||
addr:state,en,https://wiki.openstreetmap.org/wiki/Key:addr:state,2023-06-23,12,388,159,11,100
|
||||
building,en,https://wiki.openstreetmap.org/wiki/Key:building,2025-06-10,31,3774,627,158,8.91
|
||||
building,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building,2025-05-22,25,3181,544,155,8.91
|
||||
source,en,https://wiki.openstreetmap.org/wiki/Key:source,2025-08-12,27,2752,314,42,113.06
|
||||
source,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source,2024-02-07,23,2593,230,35,113.06
|
||||
highway,en,https://wiki.openstreetmap.org/wiki/Key:highway,2025-04-10,30,4126,780,314,20.35
|
||||
highway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:highway,2025-01-05,30,4141,695,313,20.35
|
||||
addr:housenumber,en,https://wiki.openstreetmap.org/wiki/Key:addr:housenumber,2025-07-24,11,330,97,20,32.04
|
||||
addr:housenumber,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:housenumber,2025-03-26,15,1655,151,78,32.04
|
||||
addr:street,en,https://wiki.openstreetmap.org/wiki/Key:addr:street,2024-10-29,12,602,101,16,36.07
|
||||
addr:street,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:street,2025-03-26,15,1655,151,78,36.07
|
||||
addr:city,en,https://wiki.openstreetmap.org/wiki/Key:addr:city,2025-07-29,15,802,105,17,29.96
|
||||
addr:city,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:city,2025-03-26,15,1655,151,78,29.96
|
||||
name,en,https://wiki.openstreetmap.org/wiki/Key:name,2025-07-25,17,2196,281,82,42.39
|
||||
name,fr,https://wiki.openstreetmap.org/wiki/FR:Key:name,2025-01-16,21,1720,187,60,42.39
|
||||
addr:postcode,en,https://wiki.openstreetmap.org/wiki/Key:addr:postcode,2024-10-29,14,382,83,11,37.14
|
||||
addr:postcode,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:postcode,2025-03-26,15,1655,151,78,37.14
|
||||
natural,en,https://wiki.openstreetmap.org/wiki/Key:natural,2025-07-17,17,2070,535,189,22.06
|
||||
natural,fr,https://wiki.openstreetmap.org/wiki/FR:Key:natural,2025-04-21,13,1499,455,174,22.06
|
||||
surface,en,https://wiki.openstreetmap.org/wiki/Key:surface,2025-06-29,24,3475,591,238,252.64
|
||||
surface,fr,https://wiki.openstreetmap.org/wiki/FR:Key:surface,2022-02-22,13,2587,461,232,252.64
|
||||
addr:country,en,https://wiki.openstreetmap.org/wiki/Key:addr:country,2024-12-01,9,184,65,11,22.96
|
||||
addr:country,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:country,2025-03-25,8,187,65,11,22.96
|
||||
landuse,en,https://wiki.openstreetmap.org/wiki/Key:landuse,2025-03-01,17,2071,446,168,39.41
|
||||
landuse,fr,https://wiki.openstreetmap.org/wiki/FR:Key:landuse,2024-08-20,19,2053,418,182,39.41
|
||||
power,en,https://wiki.openstreetmap.org/wiki/Key:power,2025-02-28,20,641,127,21,124.89
|
||||
power,fr,https://wiki.openstreetmap.org/wiki/FR:Key:power,2023-06-27,14,390,105,25,124.89
|
||||
waterway,en,https://wiki.openstreetmap.org/wiki/Key:waterway,2025-03-10,21,1830,365,118,77.94
|
||||
waterway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:waterway,2024-03-08,18,1291,272,113,77.94
|
||||
building:levels,en,https://wiki.openstreetmap.org/wiki/Key:building:levels,2025-08-13,16,1351,204,25,76.11
|
||||
building:levels,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building:levels,2024-08-01,15,1457,202,26,76.11
|
||||
amenity,en,https://wiki.openstreetmap.org/wiki/Key:amenity,2025-03-16,29,3040,914,504,128.43
|
||||
amenity,fr,https://wiki.openstreetmap.org/wiki/FR:Key:amenity,2023-07-19,22,2146,800,487,128.43
|
||||
barrier,en,https://wiki.openstreetmap.org/wiki/Key:barrier,2025-04-15,17,2137,443,173,207.98
|
||||
barrier,fr,https://wiki.openstreetmap.org/wiki/FR:Key:barrier,2022-08-16,15,542,103,18,207.98
|
||||
source:date,en,https://wiki.openstreetmap.org/wiki/Key:source:date,2023-04-01,11,395,75,10,22.47
|
||||
source:date,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source:date,2023-07-21,10,419,75,11,22.47
|
||||
service,en,https://wiki.openstreetmap.org/wiki/Key:service,2025-03-16,22,1436,218,17,83.79
|
||||
service,fr,https://wiki.openstreetmap.org/wiki/FR:Key:service,2024-03-04,11,443,100,10,83.79
|
||||
addr:state,en,https://wiki.openstreetmap.org/wiki/Key:addr:state,2023-06-23,12,289,74,11,100
|
||||
access,en,https://wiki.openstreetmap.org/wiki/Key:access,2025-08-06,31,5803,708,98,66.73
|
||||
access,fr,https://wiki.openstreetmap.org/wiki/FR:Key:access,2024-11-27,33,3200,507,84,66.73
|
||||
oneway,en,https://wiki.openstreetmap.org/wiki/Key:oneway,2025-07-17,28,2318,290,30,19.4
|
||||
oneway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:oneway,2025-06-16,14,645,108,14,19.4
|
||||
height,en,https://wiki.openstreetmap.org/wiki/Key:height,2025-07-21,24,1184,184,20,8.45
|
||||
height,fr,https://wiki.openstreetmap.org/wiki/FR:Key:height,2025-06-14,21,1285,190,21,8.45
|
||||
ref,en,https://wiki.openstreetmap.org/wiki/Key:ref,2025-07-25,26,4404,782,115,11.79
|
||||
ref,fr,https://wiki.openstreetmap.org/wiki/FR:Key:ref,2025-07-30,20,3393,460,12,11.79
|
||||
maxspeed,en,https://wiki.openstreetmap.org/wiki/Key:maxspeed,2025-08-20,30,4275,404,38,39.24
|
||||
maxspeed,fr,https://wiki.openstreetmap.org/wiki/FR:Key:maxspeed,2025-05-10,25,1401,156,23,39.24
|
||||
lanes,en,https://wiki.openstreetmap.org/wiki/Key:lanes,2025-08-21,26,2869,355,48,117.16
|
||||
lanes,fr,https://wiki.openstreetmap.org/wiki/FR:Key:lanes,2024-03-07,19,1492,167,19,117.16
|
||||
start_date,en,https://wiki.openstreetmap.org/wiki/Key:start_date,2025-08-01,22,1098,168,29,214.58
|
||||
start_date,fr,https://wiki.openstreetmap.org/wiki/FR:Key:start_date,2022-08-29,19,1097,133,22,214.58
|
||||
addr:district,en,https://wiki.openstreetmap.org/wiki/Key:addr:district,2023-11-06,11,244,76,11,109.98
|
||||
addr:district,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:district,2025-03-26,15,1655,151,78,109.98
|
||||
layer,en,https://wiki.openstreetmap.org/wiki/Key:layer,2025-01-02,16,1967,181,17,65.95
|
||||
layer,fr,https://wiki.openstreetmap.org/wiki/FR:Key:layer,2024-02-16,15,2231,162,17,65.95
|
||||
type,en,https://wiki.openstreetmap.org/wiki/Key:type,2025-05-13,20,911,200,72,334.06
|
||||
type,fr,https://wiki.openstreetmap.org/wiki/FR:Key:type,2020-11-13,10,444,78,10,334.06
|
||||
operator,en,https://wiki.openstreetmap.org/wiki/Key:operator,2025-03-12,24,1891,241,37,189.8
|
||||
operator,fr,https://wiki.openstreetmap.org/wiki/FR:Key:operator,2022-09-30,15,418,89,11,189.8
|
||||
lit,en,https://wiki.openstreetmap.org/wiki/Key:lit,2024-07-20,17,931,174,52,38.88
|
||||
lit,fr,https://wiki.openstreetmap.org/wiki/FR:Key:lit,2025-01-19,17,628,123,14,38.88
|
||||
wall,en,https://wiki.openstreetmap.org/wiki/Key:wall,2024-05-02,14,682,206,61,100
|
||||
tiger:cfcc,en,https://wiki.openstreetmap.org/wiki/Key:tiger:cfcc,2022-12-09,10,127,24,7,100
|
||||
crossing,en,https://wiki.openstreetmap.org/wiki/Key:crossing,2024-02-18,25,2678,363,34,76.98
|
||||
crossing,fr,https://wiki.openstreetmap.org/wiki/FR:Key:crossing,2025-01-20,15,1390,254,28,76.98
|
||||
tiger:county,en,https://wiki.openstreetmap.org/wiki/Key:tiger:county,2022-12-09,10,127,24,7,100
|
||||
source:addr,en,https://wiki.openstreetmap.org/wiki/Key:source:addr,2023-07-05,9,200,70,10,100
|
||||
footway,en,https://wiki.openstreetmap.org/wiki/Key:footway,2025-08-20,23,2002,369,39,99.66
|
||||
footway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:footway,2024-06-04,14,685,147,28,99.66
|
||||
ref:bag,en,https://wiki.openstreetmap.org/wiki/Key:ref:bag,2024-10-09,10,254,69,11,100
|
||||
addr:place,en,https://wiki.openstreetmap.org/wiki/Key:addr:place,2025-03-28,16,1204,154,13,136.57
|
||||
addr:place,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:place,2023-06-17,11,276,75,12,136.57
|
||||
tiger:reviewed,en,https://wiki.openstreetmap.org/wiki/Key:tiger:reviewed,2025-08-01,16,734,105,11,100
|
||||
leisure,en,https://wiki.openstreetmap.org/wiki/Key:leisure,2025-02-28,12,1084,374,180,232.43
|
||||
leisure,fr,https://wiki.openstreetmap.org/wiki/FR:Key:leisure,2021-12-29,11,951,360,186,232.43
|
||||
addr:suburb,en,https://wiki.openstreetmap.org/wiki/Key:addr:suburb,2024-02-24,14,439,89,11,1.49
|
||||
addr:suburb,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:suburb,2024-02-18,13,418,87,11,1.49
|
||||
ele,en,https://wiki.openstreetmap.org/wiki/Key:ele,2025-07-18,18,1846,165,24,104.45
|
||||
ele,fr,https://wiki.openstreetmap.org/wiki/FR:Key:ele,2024-03-02,15,1277,128,13,104.45
|
||||
tracktype,en,https://wiki.openstreetmap.org/wiki/Key:tracktype,2024-12-02,16,652,146,35,32.71
|
||||
tracktype,fr,https://wiki.openstreetmap.org/wiki/FR:Key:tracktype,2025-05-03,11,463,105,29,32.71
|
||||
addr:neighbourhood,en,https://wiki.openstreetmap.org/wiki/Key:addr:neighbourhood,2025-04-29,24,2020,235,83,100
|
||||
addr:hamlet,en,https://wiki.openstreetmap.org/wiki/Key:addr:hamlet,2024-12-05,9,142,64,11,100
|
||||
addr:province,en,https://wiki.openstreetmap.org/wiki/Key:addr:province,2022-05-04,9,156,64,11,100
|
||||
leaf_type,en,https://wiki.openstreetmap.org/wiki/Key:leaf_type,2025-01-22,15,739,201,57,114.46
|
||||
leaf_type,fr,https://wiki.openstreetmap.org/wiki/FR:Key:leaf_type,2023-07-02,14,734,220,64,114.46
|
||||
addr:full,en,https://wiki.openstreetmap.org/wiki/Key:addr:full,2025-04-29,24,2020,235,83,100
|
||||
|
|
|
Loading…
Add table
Add a link
Reference in a new issue