wiki illustrations et team osm fr

This commit is contained in:
Tykayn 2025-08-31 23:15:03 +02:00 committed by tykayn
parent d7a54458dc
commit 77ad76cc7e
13 changed files with 78859 additions and 13414 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -11,7 +11,7 @@ class WikiController extends AbstractController
/** /**
* Detects incorrect heading hierarchies in a list of sections * Detects incorrect heading hierarchies in a list of sections
* For example, h4 directly under h2 without h3 in between * For example, h4 directly under h2 without h3 in between
* *
* @param array $sections List of sections with 'level' and 'title' keys * @param array $sections List of sections with 'level' and 'title' keys
* @return array List of section indices with hierarchy errors * @return array List of section indices with hierarchy errors
*/ */
@ -19,44 +19,44 @@ class WikiController extends AbstractController
{ {
$errors = []; $errors = [];
$lastLevel = 0; $lastLevel = 0;
foreach ($sections as $index => $section) { foreach ($sections as $index => $section) {
$currentLevel = isset($section['level']) ? (int)$section['level'] : 0; $currentLevel = isset($section['level']) ? (int)$section['level'] : 0;
// Skip if level is not set or is 0 // Skip if level is not set or is 0
if ($currentLevel === 0) { if ($currentLevel === 0) {
continue; continue;
} }
// If this is the first section, just record its level // If this is the first section, just record its level
if ($lastLevel === 0) { if ($lastLevel === 0) {
$lastLevel = $currentLevel; $lastLevel = $currentLevel;
continue; continue;
} }
// Check if the level jump is more than 1 // Check if the level jump is more than 1
// For example, h2 -> h4 (skipping h3) // For example, h2 -> h4 (skipping h3)
if ($currentLevel > $lastLevel + 1) { if ($currentLevel > $lastLevel + 1) {
$errors[] = $index; $errors[] = $index;
} }
$lastLevel = $currentLevel; $lastLevel = $currentLevel;
} }
return $errors; return $errors;
} }
/** /**
* Builds an aligned list of sections for English and French * Builds an aligned list of sections for English and French
* Adds empty placeholders in the French column for sections that exist in English but not in French * Adds empty placeholders in the French column for sections that exist in English but not in French
* *
* @param array $sectionComparison Section comparison data with 'common', 'en_only', and 'fr_only' keys * @param array $sectionComparison Section comparison data with 'common', 'en_only', and 'fr_only' keys
* @return array Aligned section list with 'en' and 'fr' columns * @return array Aligned section list with 'en' and 'fr' columns
*/ */
private function buildAlignedSectionList(array $sectionComparison): array private function buildAlignedSectionList(array $sectionComparison): array
{ {
$alignedSections = []; $alignedSections = [];
// First, process common sections (they already have both en and fr) // First, process common sections (they already have both en and fr)
if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) {
foreach ($sectionComparison['common'] as $section) { foreach ($sectionComparison['common'] as $section) {
@ -66,7 +66,7 @@ class WikiController extends AbstractController
]; ];
} }
} }
// Then, process English-only sections and add empty placeholders for French // Then, process English-only sections and add empty placeholders for French
if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) { if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) {
foreach ($sectionComparison['en_only'] as $section) { foreach ($sectionComparison['en_only'] as $section) {
@ -83,7 +83,7 @@ class WikiController extends AbstractController
]; ];
} }
} }
// Finally, process French-only sections (these will be shown at the end) // Finally, process French-only sections (these will be shown at the end)
if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) { if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) {
foreach ($sectionComparison['fr_only'] as $section) { foreach ($sectionComparison['fr_only'] as $section) {
@ -100,9 +100,10 @@ class WikiController extends AbstractController
]; ];
} }
} }
return $alignedSections; return $alignedSections;
} }
#[Route('/wiki/recent-changes', name: 'app_admin_wiki_recent_changes')] #[Route('/wiki/recent-changes', name: 'app_admin_wiki_recent_changes')]
public function recentChanges(): Response public function recentChanges(): Response
{ {
@ -617,7 +618,7 @@ class WikiController extends AbstractController
'status' => $proposal['status'] ?? 'Voting', 'status' => $proposal['status'] ?? 'Voting',
'type' => 'voting' 'type' => 'voting'
]; ];
// Add voting information if available // Add voting information if available
if (isset($proposal['votes'])) { if (isset($proposal['votes'])) {
$formattedProposal['votes'] = $proposal['votes']; $formattedProposal['votes'] = $proposal['votes'];
@ -626,7 +627,7 @@ class WikiController extends AbstractController
$formattedProposal['oppose_percentage'] = $proposal['oppose_percentage'] ?? 0; $formattedProposal['oppose_percentage'] = $proposal['oppose_percentage'] ?? 0;
$formattedProposal['abstain_percentage'] = $proposal['abstain_percentage'] ?? 0; $formattedProposal['abstain_percentage'] = $proposal['abstain_percentage'] ?? 0;
} }
$formattedProposals[] = $formattedProposal; $formattedProposals[] = $formattedProposal;
} }
@ -692,21 +693,21 @@ class WikiController extends AbstractController
'page' => $randomPage 'page' => $randomPage
]); ]);
} }
#[Route('/wiki/create-french/{key}', name: 'app_admin_wiki_create_french')] #[Route('/wiki/create-french/{key}', name: 'app_admin_wiki_create_french')]
public function createFrench(string $key): Response public function createFrench(string $key): Response
{ {
// Construct the URLs for the English page and the French page creation form // Construct the URLs for the English page and the French page creation form
$englishUrl = "https://wiki.openstreetmap.org/wiki/Key:{$key}"; $englishUrl = "https://wiki.openstreetmap.org/wiki/Key:{$key}";
$frenchEditUrl = "https://wiki.openstreetmap.org/w/index.php?title=FR:{$key}&action=edit"; $frenchEditUrl = "https://wiki.openstreetmap.org/w/index.php?title=FR:{$key}&action=edit";
return $this->render('admin/wiki_create_french.html.twig', [ return $this->render('admin/wiki_create_french.html.twig', [
'key' => $key, 'key' => $key,
'english_url' => $englishUrl, 'english_url' => $englishUrl,
'french_edit_url' => $frenchEditUrl 'french_edit_url' => $frenchEditUrl
]); ]);
} }
#[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')] #[Route('/wiki/archived-proposals', name: 'app_admin_wiki_archived_proposals')]
public function archivedProposals(\Symfony\Component\HttpFoundation\Request $request): Response public function archivedProposals(\Symfony\Component\HttpFoundation\Request $request): Response
{ {
@ -723,7 +724,7 @@ class WikiController extends AbstractController
if ($forceRefresh) { if ($forceRefresh) {
$this->refreshArchivedProposalsData($limit); $this->refreshArchivedProposalsData($limit);
$this->addFlash('success', 'Les données des propositions archivées ont été rafraîchies.'); $this->addFlash('success', 'Les données des propositions archivées ont été rafraîchies.');
// Preserve the limit parameter in the redirect if it was provided // Preserve the limit parameter in the redirect if it was provided
if ($limit) { if ($limit) {
return $this->redirectToRoute('app_admin_wiki_archived_proposals', ['limit' => $limit]); return $this->redirectToRoute('app_admin_wiki_archived_proposals', ['limit' => $limit]);
@ -751,7 +752,7 @@ class WikiController extends AbstractController
if ($diff->days > 1) { if ($diff->days > 1) {
$this->refreshArchivedProposalsData($limit); $this->refreshArchivedProposalsData($limit);
$this->addFlash('info', 'Les données des propositions archivées ont été automatiquement mises à jour car elles dataient de plus d\'un jour.'); $this->addFlash('info', 'Les données des propositions archivées ont été automatiquement mises à jour car elles dataient de plus d\'un jour.');
// Preserve the limit parameter in the redirect if it was provided // Preserve the limit parameter in the redirect if it was provided
if ($limit) { if ($limit) {
return $this->redirectToRoute('app_admin_wiki_archived_proposals', ['limit' => $limit]); return $this->redirectToRoute('app_admin_wiki_archived_proposals', ['limit' => $limit]);
@ -766,7 +767,7 @@ class WikiController extends AbstractController
// Check if the file was created // Check if the file was created
if (file_exists($jsonFile)) { if (file_exists($jsonFile)) {
$this->addFlash('success', 'Le fichier des propositions archivées a été généré avec succès.'); $this->addFlash('success', 'Le fichier des propositions archivées a été généré avec succès.');
// Preserve the limit parameter in the redirect if it was provided // Preserve the limit parameter in the redirect if it was provided
if ($limit) { if ($limit) {
return $this->redirectToRoute('app_admin_wiki_archived_proposals', ['limit' => $limit]); return $this->redirectToRoute('app_admin_wiki_archived_proposals', ['limit' => $limit]);
@ -784,10 +785,10 @@ class WikiController extends AbstractController
'limit' => $limit 'limit' => $limit
]); ]);
} }
/** /**
* Refresh the archived proposals data by running the fetch_archived_proposals.py script * Refresh the archived proposals data by running the fetch_archived_proposals.py script
* *
* @param int|null $limit Optional limit for the number of proposals to process * @param int|null $limit Optional limit for the number of proposals to process
*/ */
private function refreshArchivedProposalsData(?int $limit = null): void private function refreshArchivedProposalsData(?int $limit = null): void
@ -796,12 +797,12 @@ class WikiController extends AbstractController
$scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/fetch_archived_proposals.py'; $scriptPath = $this->getParameter('kernel.project_dir') . '/wiki_compare/fetch_archived_proposals.py';
if (file_exists($scriptPath)) { if (file_exists($scriptPath)) {
$command = 'python3 ' . $scriptPath; $command = 'python3 ' . $scriptPath;
// Add limit parameter if provided // Add limit parameter if provided
if ($limit !== null) { if ($limit !== null) {
$command .= ' --limit ' . $limit; $command .= ' --limit ' . $limit;
} }
exec($command . ' 2>&1', $output, $returnCode); exec($command . ' 2>&1', $output, $returnCode);
if ($returnCode !== 0) { if ($returnCode !== 0) {
@ -873,6 +874,7 @@ class WikiController extends AbstractController
$missingTranslations[$key] = $languages['en']; $missingTranslations[$key] = $languages['en'];
} }
} }
// Calculate differences between English and French versions // Calculate differences between English and French versions
foreach ($wikiPages as $key => $languages) { foreach ($wikiPages as $key => $languages) {
@ -1027,80 +1029,80 @@ class WikiController extends AbstractController
// Get link comparison data // Get link comparison data
$linkComparison = $page['link_comparison'] ?? null; $linkComparison = $page['link_comparison'] ?? null;
// Sort links alphabetically by URL if link comparison exists // Sort links alphabetically by URL if link comparison exists
if ($linkComparison) { if ($linkComparison) {
// Sort English-only links // Sort English-only links
if (isset($linkComparison['en_only']) && is_array($linkComparison['en_only'])) { if (isset($linkComparison['en_only']) && is_array($linkComparison['en_only'])) {
usort($linkComparison['en_only'], function($a, $b) { usort($linkComparison['en_only'], function ($a, $b) {
return strcmp($a['href'], $b['href']); return strcmp($a['href'], $b['href']);
}); });
} }
// Sort French-only links // Sort French-only links
if (isset($linkComparison['fr_only']) && is_array($linkComparison['fr_only'])) { if (isset($linkComparison['fr_only']) && is_array($linkComparison['fr_only'])) {
usort($linkComparison['fr_only'], function($a, $b) { usort($linkComparison['fr_only'], function ($a, $b) {
return strcmp($a['href'], $b['href']); return strcmp($a['href'], $b['href']);
}); });
} }
// Sort common links // Sort common links
if (isset($linkComparison['common']) && is_array($linkComparison['common'])) { if (isset($linkComparison['common']) && is_array($linkComparison['common'])) {
usort($linkComparison['common'], function($a, $b) { usort($linkComparison['common'], function ($a, $b) {
return strcmp($a['en']['href'], $b['en']['href']); return strcmp($a['en']['href'], $b['en']['href']);
}); });
} }
} }
// Get section comparison data and filter out "Contents" sections and navigation sections // Get section comparison data and filter out "Contents" sections and navigation sections
$sectionComparison = $page['section_comparison'] ?? null; $sectionComparison = $page['section_comparison'] ?? null;
// Sections to exclude from comparison (navigation elements) // Sections to exclude from comparison (navigation elements)
$excludedSections = [ $excludedSections = [
'Contents', 'Sommaire', 'Contents', 'Sommaire',
'Personal tools', 'Namespaces', 'Views', 'Search', 'Site', 'Tools', 'In other projects' 'Personal tools', 'Namespaces', 'Views', 'Search', 'Site', 'Tools', 'In other projects'
]; ];
// Filter out excluded sections if section comparison exists // Filter out excluded sections if section comparison exists
if ($sectionComparison) { if ($sectionComparison) {
// Filter common sections // Filter common sections
if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) {
$sectionComparison['common'] = array_filter($sectionComparison['common'], function($section) use ($excludedSections) { $sectionComparison['common'] = array_filter($sectionComparison['common'], function ($section) use ($excludedSections) {
// Skip if either English or French title is in the excluded list // Skip if either English or French title is in the excluded list
return !(in_array($section['en']['title'], $excludedSections) || in_array($section['fr']['title'], $excludedSections)); return !(in_array($section['en']['title'], $excludedSections) || in_array($section['fr']['title'], $excludedSections));
}); });
// Re-index array // Re-index array
$sectionComparison['common'] = array_values($sectionComparison['common']); $sectionComparison['common'] = array_values($sectionComparison['common']);
} }
// Filter English-only sections // Filter English-only sections
if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) { if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) {
$sectionComparison['en_only'] = array_filter($sectionComparison['en_only'], function($section) use ($excludedSections) { $sectionComparison['en_only'] = array_filter($sectionComparison['en_only'], function ($section) use ($excludedSections) {
return !in_array($section['title'], $excludedSections); return !in_array($section['title'], $excludedSections);
}); });
// Re-index array // Re-index array
$sectionComparison['en_only'] = array_values($sectionComparison['en_only']); $sectionComparison['en_only'] = array_values($sectionComparison['en_only']);
} }
// Filter French-only sections // Filter French-only sections
if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) { if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) {
$sectionComparison['fr_only'] = array_filter($sectionComparison['fr_only'], function($section) use ($excludedSections) { $sectionComparison['fr_only'] = array_filter($sectionComparison['fr_only'], function ($section) use ($excludedSections) {
return !in_array($section['title'], $excludedSections); return !in_array($section['title'], $excludedSections);
}); });
// Re-index array // Re-index array
$sectionComparison['fr_only'] = array_values($sectionComparison['fr_only']); $sectionComparison['fr_only'] = array_values($sectionComparison['fr_only']);
} }
} }
// Calculate adjusted section counts (excluding "Contents" sections) // Calculate adjusted section counts (excluding "Contents" sections)
$enSectionCount = $enPage['sections']; $enSectionCount = $enPage['sections'];
$frSectionCount = $frPage['sections']; $frSectionCount = $frPage['sections'];
// Adjust section counts if we have section comparison data // Adjust section counts if we have section comparison data
if ($sectionComparison) { if ($sectionComparison) {
// Count how many sections were filtered out // Count how many sections were filtered out
$filteredCount = 0; $filteredCount = 0;
// Check common sections that were filtered // Check common sections that were filtered
if (isset($page['section_comparison']['common']) && is_array($page['section_comparison']['common'])) { if (isset($page['section_comparison']['common']) && is_array($page['section_comparison']['common'])) {
foreach ($page['section_comparison']['common'] as $section) { foreach ($page['section_comparison']['common'] as $section) {
@ -1109,7 +1111,7 @@ class WikiController extends AbstractController
} }
} }
} }
// Check English-only sections that were filtered // Check English-only sections that were filtered
if (isset($page['section_comparison']['en_only']) && is_array($page['section_comparison']['en_only'])) { if (isset($page['section_comparison']['en_only']) && is_array($page['section_comparison']['en_only'])) {
foreach ($page['section_comparison']['en_only'] as $section) { foreach ($page['section_comparison']['en_only'] as $section) {
@ -1118,7 +1120,7 @@ class WikiController extends AbstractController
} }
} }
} }
// Check French-only sections that were filtered // Check French-only sections that were filtered
if (isset($page['section_comparison']['fr_only']) && is_array($page['section_comparison']['fr_only'])) { if (isset($page['section_comparison']['fr_only']) && is_array($page['section_comparison']['fr_only'])) {
foreach ($page['section_comparison']['fr_only'] as $section) { foreach ($page['section_comparison']['fr_only'] as $section) {
@ -1127,47 +1129,47 @@ class WikiController extends AbstractController
} }
} }
} }
// Adjust section counts // Adjust section counts
$enSectionCount -= $filteredCount; $enSectionCount -= $filteredCount;
$frSectionCount -= $filteredCount; $frSectionCount -= $filteredCount;
} }
// Check for incorrect heading hierarchies // Check for incorrect heading hierarchies
$enHierarchyErrors = []; $enHierarchyErrors = [];
$frHierarchyErrors = []; $frHierarchyErrors = [];
// Check English sections // Check English sections
if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) { if (isset($sectionComparison['en_only']) && is_array($sectionComparison['en_only'])) {
$enHierarchyErrors = $this->detectHeadingHierarchyErrors($sectionComparison['en_only']); $enHierarchyErrors = $this->detectHeadingHierarchyErrors($sectionComparison['en_only']);
} }
// Also check common sections (English side) // Also check common sections (English side)
if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) {
$commonEnSections = array_map(function($section) { $commonEnSections = array_map(function ($section) {
return $section['en']; return $section['en'];
}, $sectionComparison['common']); }, $sectionComparison['common']);
$enHierarchyErrors = array_merge($enHierarchyErrors, $this->detectHeadingHierarchyErrors($commonEnSections)); $enHierarchyErrors = array_merge($enHierarchyErrors, $this->detectHeadingHierarchyErrors($commonEnSections));
} }
// Check French sections // Check French sections
if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) { if (isset($sectionComparison['fr_only']) && is_array($sectionComparison['fr_only'])) {
$frHierarchyErrors = $this->detectHeadingHierarchyErrors($sectionComparison['fr_only']); $frHierarchyErrors = $this->detectHeadingHierarchyErrors($sectionComparison['fr_only']);
} }
// Also check common sections (French side) // Also check common sections (French side)
if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) { if (isset($sectionComparison['common']) && is_array($sectionComparison['common'])) {
$commonFrSections = array_map(function($section) { $commonFrSections = array_map(function ($section) {
return $section['fr']; return $section['fr'];
}, $sectionComparison['common']); }, $sectionComparison['common']);
$frHierarchyErrors = array_merge($frHierarchyErrors, $this->detectHeadingHierarchyErrors($commonFrSections)); $frHierarchyErrors = array_merge($frHierarchyErrors, $this->detectHeadingHierarchyErrors($commonFrSections));
} }
// Build aligned section list for better visualization of missing sections // Build aligned section list for better visualization of missing sections
$alignedSections = $this->buildAlignedSectionList($sectionComparison); $alignedSections = $this->buildAlignedSectionList($sectionComparison);
$detailedComparison = [ $detailedComparison = [
'section_comparison' => $sectionComparison, 'section_comparison' => $sectionComparison,
'aligned_sections' => $alignedSections, 'aligned_sections' => $alignedSections,
@ -1355,11 +1357,11 @@ class WikiController extends AbstractController
if ($frPage && isset($frPage['url']) && is_array($frPage['url'])) { if ($frPage && isset($frPage['url']) && is_array($frPage['url'])) {
$frPage['url'] = json_encode($frPage['url']); $frPage['url'] = json_encode($frPage['url']);
} }
if ($enPage && isset($enPage['url']) && is_array($enPage['url'])) { if ($enPage && isset($enPage['url']) && is_array($enPage['url'])) {
$enPage['url'] = json_encode($enPage['url']); $enPage['url'] = json_encode($enPage['url']);
} }
return $this->render('admin/wiki_compare.html.twig', [ return $this->render('admin/wiki_compare.html.twig', [
'key' => $key, 'key' => $key,
'en_page' => $enPage, 'en_page' => $enPage,

View file

@ -18,6 +18,7 @@
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead class="thead-dark"> <thead class="thead-dark">
<tr> <tr>
<th rowspan="2">Image</th>
<th rowspan="2">Clé</th> <th rowspan="2">Clé</th>
<th colspan="4" class="text-center">Différences FR vs EN</th> <th colspan="4" class="text-center">Différences FR vs EN</th>
<th rowspan="2" class="text-center">Score de<br>décrépitude</th> <th rowspan="2" class="text-center">Score de<br>décrépitude</th>
@ -34,6 +35,11 @@
{% for key, languages in wiki_pages %} {% for key, languages in wiki_pages %}
{% if languages['en'] is defined and languages['fr'] is defined %} {% if languages['en'] is defined and languages['fr'] is defined %}
<tr> <tr>
<td>
<img src="{{ languages['en'].description_img_url }}" alt="image" style="height: 2rem;">
</td>
<td> <td>
<strong>{{ key }}</strong> <strong>{{ key }}</strong>
</td> </td>
@ -147,7 +153,8 @@
<i class="bi bi-flag-fill"></i> EN <i class="bi bi-flag-fill"></i> EN
</a> </a>
<a href="{{ path('app_admin_wiki_create_french', {'key': key}) }}" <a href="{{ path('app_admin_wiki_create_french', {'key': key}) }}"
class="btn btn-sm btn-outline-primary" title="Créer une traduction française"> class="btn btn-sm btn-outline-primary"
title="Créer une traduction française">
<i class="bi bi-translate"></i> créer FR <i class="bi bi-translate"></i> créer FR
</a> </a>
{# <a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" #} {# <a href="{{ path('app_admin_wiki_compare', {'key': key}) }}" #}
@ -187,43 +194,43 @@
{{ parent() }} {{ parent() }}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
// Collect data from the table // Collect data from the table
const labels = []; const labels = [];
const scores = []; const scores = [];
const colors = []; const colors = [];
{% for key, languages in wiki_pages %} {% for key, languages in wiki_pages %}
{% if languages['en'] is defined and languages['fr'] is defined %} {% if languages['en'] is defined and languages['fr'] is defined %}
labels.push("{{ key }}"); labels.push("{{ key }}");
{% set score = languages['en'].staleness_score|default(0) %} {% set score = languages['en'].staleness_score|default(0) %}
scores.push({{ score }}); scores.push({{ score }});
// Set color based on score // Set color based on score
{% if score > 50 %} {% if score > 50 %}
colors.push('rgba(220, 53, 69, 0.7)'); // danger colors.push('rgba(220, 53, 69, 0.7)'); // danger
{% elseif score > 20 %} {% elseif score > 20 %}
colors.push('rgba(255, 193, 7, 0.7)'); // warning colors.push('rgba(255, 193, 7, 0.7)'); // warning
{% else %} {% else %}
colors.push('rgba(25, 135, 84, 0.7)'); // success colors.push('rgba(25, 135, 84, 0.7)'); // success
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
// Sort data by score (descending) // Sort data by score (descending)
const indices = Array.from(Array(scores.length).keys()) const indices = Array.from(Array(scores.length).keys())
.sort((a, b) => scores[b] - scores[a]); .sort((a, b) => scores[b] - scores[a]);
const sortedLabels = indices.map(i => labels[i]); const sortedLabels = indices.map(i => labels[i]);
const sortedScores = indices.map(i => scores[i]); const sortedScores = indices.map(i => scores[i]);
const sortedColors = indices.map(i => colors[i]); const sortedColors = indices.map(i => colors[i]);
// Limit to top 20 pages for readability // Limit to top 20 pages for readability
const displayLimit = 20; const displayLimit = 20;
const displayLabels = sortedLabels.slice(0, displayLimit); const displayLabels = sortedLabels.slice(0, displayLimit);
const displayScores = sortedScores.slice(0, displayLimit); const displayScores = sortedScores.slice(0, displayLimit);
const displayColors = sortedColors.slice(0, displayLimit); const displayColors = sortedColors.slice(0, displayLimit);
// Create the chart // Create the chart
const ctx = document.getElementById('decrepitudeChart').getContext('2d'); const ctx = document.getElementById('decrepitudeChart').getContext('2d');
new Chart(ctx, { new Chart(ctx, {
@ -247,7 +254,7 @@
}, },
tooltip: { tooltip: {
callbacks: { callbacks: {
label: function(context) { label: function (context) {
return `Score: ${context.raw}`; return `Score: ${context.raw}`;
} }
} }

View file

@ -58,6 +58,37 @@
<a href="https://wiki.openstreetmap.org/Key:{{ key }}">en</a> <a href="https://wiki.openstreetmap.org/Key:{{ key }}">en</a>
</h1> </h1>
<p class="lead">Comparaison détaillée des pages wiki en français et en anglais pour la clé OSM "{{ key }}".</p> <p class="lead">Comparaison détaillée des pages wiki en français et en anglais pour la clé OSM "{{ key }}".</p>
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header bg-primary text-white">
<h3>Page anglaise</h3>
</div>
<div class="card-body text-center">
{% if en_page.description_img_url is defined and en_page.description_img_url %}
<img src="{{ en_page.description_img_url }}" alt="{{ key }}" class="img-fluid" style="max-height: 200px; object-fit: contain;">
{% else %}
<div class="alert alert-secondary">Pas d'image d'illustration</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header bg-info text-white">
<h3>Page française</h3>
</div>
<div class="card-body text-center">
{% if fr_page is defined and fr_page is not null and fr_page.description_img_url is defined and fr_page.description_img_url %}
<img src="{{ fr_page.description_img_url }}" alt="{{ key }}" class="img-fluid" style="max-height: 200px; object-fit: contain;">
{% else %}
<div class="alert alert-secondary">Pas d'image d'illustration</div>
{% endif %}
</div>
</div>
</div>
</div>
{% if fr_page is defined and fr_page is not null %} {% if fr_page is defined and fr_page is not null %}
@ -212,88 +243,87 @@
</div> </div>
{% endif %} {% endif %}
{# {% if detailed_comparison and detailed_comparison.media_comparison %} #} {% if detailed_comparison and detailed_comparison.media_comparison %}
{# <div class="card mb-4"> #} <div class="card mb-4">
{# <div class="card-header"> #} <div class="card-header">
{# <h2>Comparaison des médias</h2> #} <h2>Comparaison des médias</h2>
{# </div> #} </div>
{# <div class="card-body"> #} <div class="card-body">
{# <div class="row"> #} <div class="row">
{# <div class="col-md-6"> #} <div class="col-md-6">
{# <div class="card h-100"> #} <div class="card h-100">
{# <div class="card-header bg-primary text-white"> #} <div class="card-header bg-primary text-white">
{# <h3>Images en anglais</h3> #} <h3>Images en anglais</h3>
{# <span class="badge bg-light text-dark">{{ en_page.media_count|default(0) }} images</span> #} <span class="badge bg-light text-dark">{{ en_page.media_count|default(0) }} images</span>
{# </div> #} </div>
{# <div class="card-body"> #} <div class="card-body">
<h4>Images uniquement en anglais
({{ detailed_comparison.media_comparison.en_only|length }} uniques
sur {{ detailed_comparison.media_comparison.en_only_count }} total)</h4>
<div class="row">
{% for media in detailed_comparison.media_comparison.en_only %}
<div class="col-12 mb-2">
<div class="card border-warning">
<img src="{{ media.src }}" class="card-img-top"
alt="{{ media.alt }}"
style="max-height: 150px; object-fit: contain;">
<div class="card-body p-2">
<p class="card-text small">{{ media.alt }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-info text-white">
<h3>Images en français</h3>
<span class="badge bg-light text-dark">{{ fr_page.media_count|default(0) }} images</span>
</div>
<div class="card-body">
<h4>Images communes ({{ detailed_comparison.media_comparison.common|length }})</h4>
<div class="row mb-3">
{% for media in detailed_comparison.media_comparison.common %}
<div class="col-md-6 mb-2">
<div class="card">
<img src="{{ media.fr.src }}" class="card-img-top"
alt="{{ media.fr.alt }}"
style="max-height: 150px; object-fit: contain;">
<div class="card-body p-2">
<p class="card-text small">{{ media.fr.alt }}</p>
</div>
</div>
</div>
{% endfor %}
</div>
{# <h4>Images uniquement en anglais #} <h4>Images uniquement en français
{# ({{ detailed_comparison.media_comparison.en_only|length }} uniques #} ({{ detailed_comparison.media_comparison.fr_only|length }} uniques
{# sur {{ detailed_comparison.media_comparison.en_only_count }} total)</h4> #} sur {{ detailed_comparison.media_comparison.fr_only_count }} total)</h4>
{# <div class="row"> #} <div class="row">
{# {% for media in detailed_comparison.media_comparison.en_only %} #} {% for media in detailed_comparison.media_comparison.fr_only %}
{# <div class="col-12 mb-2"> #} <div class="col-12 mb-2">
{# <div class="card border-warning"> #} <div class="card border-info">
{# <img src="{{ media.src }}" class="card-img-top" #} <img src="{{ media.src }}" class="card-img-top"
{# alt="{{ media.alt }}" #} alt="{{ media.alt }}"
{# style="max-height: 150px; object-fit: contain;"> #} style="max-height: 150px; object-fit: contain;">
{# <div class="card-body p-2"> #} <div class="card-body p-2">
{# <p class="card-text small">{{ media.alt }}</p> #} <p class="card-text small">{{ media.alt }}</p>
{# </div> #} </div>
{# </div> #} </div>
{# </div> #} </div>
{# {% endfor %} #} {% endfor %}
{# </div> #} </div>
{# </div> #} </div>
{# </div> #} </div>
{# </div> #} </div>
{# <div class="col-md-6"> #} </div>
{# <div class="card h-100"> #} </div>
{# <div class="card-header bg-info text-white"> #} </div>
{# <h3>Images en français</h3> #} {% endif %}
{# <span class="badge bg-light text-dark">{{ fr_page.media_count|default(0) }} images</span> #}
{# </div> #}
{# <div class="card-body"> #}
{# <h4>Images communes ({{ detailed_comparison.media_comparison.common|length }})</h4> #}
{# <div class="row mb-3"> #}
{# {% for media in detailed_comparison.media_comparison.common %} #}
{# <div class="col-md-6 mb-2"> #}
{# <div class="card"> #}
{# <img src="{{ media.fr.src }}" class="card-img-top" #}
{# alt="{{ media.fr.alt }}" #}
{# style="max-height: 150px; object-fit: contain;"> #}
{# <div class="card-body p-2"> #}
{# <p class="card-text small">{{ media.fr.alt }}</p> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# {% endfor %} #}
{# </div> #}
{# <h4>Images uniquement en français #}
{# ({{ detailed_comparison.media_comparison.fr_only|length }} uniques #}
{# sur {{ detailed_comparison.media_comparison.fr_only_count }} total)</h4> #}
{# <div class="row"> #}
{# {% for media in detailed_comparison.media_comparison.fr_only %} #}
{# <div class="col-12 mb-2"> #}
{# <div class="card border-info"> #}
{# <img src="{{ media.src }}" class="card-img-top" #}
{# alt="{{ media.alt }}" #}
{# style="max-height: 150px; object-fit: contain;"> #}
{# <div class="card-body p-2"> #}
{# <p class="card-text small">{{ media.alt }}</p> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# {% endfor %} #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# </div> #}
{# {% endif %} #}
{# {% if detailed_comparison and detailed_comparison.link_comparison %} #} {# {% if detailed_comparison and detailed_comparison.link_comparison %} #}
{# <div class="card mb-4"> #} {# <div class="card mb-4"> #}

View file

@ -89,10 +89,20 @@
{% for page in pages %} {% for page in pages %}
<tr> <tr>
<td> <td>
<strong>{{ page.title }}</strong> <div class="d-flex align-items-center">
{% if page.is_english %} {% if page.description_img_url is defined and page.description_img_url %}
<span class="badge bg-success">Priorité</span> <div class="me-3">
{% endif %} <img src="{{ page.description_img_url }}" alt="{{ page.title }}"
style="max-width: 80px; max-height: 60px; object-fit: contain;">
</div>
{% endif %}
<div>
<strong>{{ page.title }}</strong>
{% if page.is_english %}
<span class="badge bg-success">Priorité</span>
{% endif %}
</div>
</div>
</td> </td>
<td> <td>
{% if page.outdatedness_score is defined %} {% if page.outdatedness_score is defined %}
@ -171,9 +181,18 @@ document.addEventListener('DOMContentLoaded', function() {
// Format titles in MediaWiki format // Format titles in MediaWiki format
let mediawikiText = ''; let mediawikiText = '';
titleElements.forEach(function(element) { const rows = englishSection.querySelectorAll('tbody tr');
const title = element.textContent.trim();
mediawikiText += '* [[' + title + ']]\n'; rows.forEach(function(row) {
const title = row.querySelector('td:first-child strong').textContent.trim();
const imgElement = row.querySelector('td:first-child img');
if (imgElement) {
const imgSrc = imgElement.getAttribute('src');
mediawikiText += '* [[' + title + ']] - Image: ' + imgSrc + '\n';
} else {
mediawikiText += '* [[' + title + ']]\n';
}
}); });
// Copy to clipboard // Copy to clipboard

View file

@ -27,6 +27,7 @@ import os
import re import re
import random import random
import hashlib import hashlib
import csv
from datetime import datetime, timedelta from datetime import datetime, timedelta
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -41,10 +42,33 @@ logger = logging.getLogger(__name__)
# Constants # Constants
OUTPUT_FILE = "pages_unavailable_in_french.json" OUTPUT_FILE = "pages_unavailable_in_french.json"
WIKI_PAGES_CSV = "wiki_pages.csv"
BASE_URL = "https://wiki.openstreetmap.org/wiki/Category:Pages_unavailable_in_French" BASE_URL = "https://wiki.openstreetmap.org/wiki/Category:Pages_unavailable_in_French"
WIKI_BASE_URL = "https://wiki.openstreetmap.org" WIKI_BASE_URL = "https://wiki.openstreetmap.org"
CACHE_DURATION = timedelta(hours=1) # Cache duration of 1 hour CACHE_DURATION = timedelta(hours=1) # Cache duration of 1 hour
def read_wiki_pages_csv():
"""
Read the wiki_pages.csv file and create a mapping of URLs to description_img_url values
Returns:
dict: Dictionary mapping URLs to description_img_url values
"""
url_to_img_map = {}
try:
with open(WIKI_PAGES_CSV, 'r', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
if 'url' in row and 'description_img_url' in row and row['description_img_url']:
url_to_img_map[row['url']] = row['description_img_url']
logger.info(f"Read {len(url_to_img_map)} image URLs from {WIKI_PAGES_CSV}")
return url_to_img_map
except (IOError, csv.Error) as e:
logger.error(f"Error reading {WIKI_PAGES_CSV}: {e}")
return {}
def is_cache_fresh(): def is_cache_fresh():
""" """
Check if the cache file exists and is less than CACHE_DURATION old Check if the cache file exists and is less than CACHE_DURATION old
@ -273,6 +297,9 @@ def main():
logger.info(f"Use --force to update anyway") logger.info(f"Use --force to update anyway")
return return
# Read image URLs from wiki_pages.csv
url_to_img_map = read_wiki_pages_csv()
# Scrape pages # Scrape pages
pages = scrape_all_pages() pages = scrape_all_pages()
@ -280,6 +307,11 @@ def main():
logger.error("No pages found") logger.error("No pages found")
return return
# Add description_img_url to pages
for page in pages:
if page["url"] in url_to_img_map:
page["description_img_url"] = url_to_img_map[page["url"]]
# Save results # Save results
success = save_results(pages, args.dry_run) success = save_results(pages, args.dry_run)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

View file

@ -18,5 +18,65 @@
{ {
"key": "addr:street", "key": "addr:street",
"count": 161005721 "count": 161005721
},
{
"key": "addr:city",
"count": 123355107
},
{
"key": "name",
"count": 109342549
},
{
"key": "addr:postcode",
"count": 107014659
},
{
"key": "natural",
"count": 84723029
},
{
"key": "surface",
"count": 72309071
},
{
"key": "addr:country",
"count": 50567842
},
{
"key": "landuse",
"count": 48196369
},
{
"key": "power",
"count": 44787307
},
{
"key": "waterway",
"count": 37279458
},
{
"key": "building:levels",
"count": 36502866
},
{
"key": "amenity",
"count": 30994353
},
{
"key": "barrier",
"count": 30164354
},
{
"key": "source:date",
"count": 29112775
},
{
"key": "service",
"count": 28396250
},
{
"key": "addr:state",
"count": 25367076
} }
] ]

View file

@ -211,6 +211,145 @@ def fetch_wiki_page(key, language='en'):
# Get media details (src and alt text) # Get media details (src and alt text)
media_details = [] media_details = []
# Extract description image specifically
# Try multiple selectors to find the description image
description_img = None
# Debug: Log the key we're processing
logger.info(f"Looking for description image for key '{key}' in {language}")
# Function to filter out OSM logo and small icons
def is_relevant_image(img):
src = img.get('src', '')
# Skip OSM logo
if 'osm_logo' in src:
return False
# Skip small icons (usually less than 30px)
width = img.get('width')
if width and int(width) < 30:
return False
height = img.get('height')
if height and int(height) < 30:
return False
return True
# Special case for highway key - directly target the image we want
if key == 'highway':
# Try to find the specific image in figure elements
highway_img_elements = content.select('figure.mw-halign-center img')
logger.info(f" Highway specific selector 'figure.mw-halign-center img' found {len(highway_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in highway_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images for highway")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using highway-specific image: {description_img.get('src', '')}")
# If not found with highway-specific selector, try the td.d_image selector
if not description_img:
description_img_elements = content.select('td.d_image img')
logger.info(f" Selector 'td.d_image img' found {len(description_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in description_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in td.d_image")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using image from 'td.d_image img': {description_img.get('src', '')}")
# If still not found, try the specific selector for .description img.mw-file-element
if not description_img:
description_img_elements = content.select('.description img.mw-file-element')
logger.info(f" Selector '.description img.mw-file-element' found {len(description_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in description_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in .description")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using image from '.description img.mw-file-element': {description_img.get('src', '')}")
# If still not found, try images in figures within the description box
if not description_img:
description_img_elements = content.select('.description figure img')
logger.info(f" Selector '.description figure img' found {len(description_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in description_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in .description figure")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using image from '.description figure img': {description_img.get('src', '')}")
# If still not found, try any image in the description box
if not description_img:
description_img_elements = content.select('.description img')
logger.info(f" Selector '.description img' found {len(description_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in description_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in .description general")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using image from '.description img': {description_img.get('src', '')}")
# If still not found, try images in the DescriptionBox table
if not description_img:
description_img_elements = content.select('table.DescriptionBox img')
logger.info(f" Selector 'table.DescriptionBox img' found {len(description_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in description_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in DescriptionBox")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using image from 'table.DescriptionBox img': {description_img.get('src', '')}")
# If still not found, try images in figure elements anywhere in the content
if not description_img:
description_img_elements = content.select('figure img')
logger.info(f" Selector 'figure img' found {len(description_img_elements)} elements")
# Filter for relevant images
relevant_images = [img for img in description_img_elements if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in figure elements")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using image from 'figure img': {description_img.get('src', '')}")
# If we still don't have an image, use any image that's not the OSM logo
if not description_img:
all_images = content.select('img')
relevant_images = [img for img in all_images if is_relevant_image(img)]
logger.info(f" Found {len(relevant_images)} relevant images in the entire page")
if relevant_images:
description_img = relevant_images[0]
logger.info(f" Using fallback image: {description_img.get('src', '')}")
# Process the found image
description_img_url = None
if description_img:
src = description_img.get('src', '')
if src:
# Make relative URLs absolute
if src.startswith('//'):
src = 'https:' + src
elif src.startswith('/'):
src = 'https://wiki.openstreetmap.org' + src
description_img_url = src
# Process all images
for img in media_elements: for img in media_elements:
src = img.get('src', '') src = img.get('src', '')
if src: if src:
@ -251,7 +390,8 @@ def fetch_wiki_page(key, language='en'):
'link_details': link_details, 'link_details': link_details,
'media_count': media_count, 'media_count': media_count,
'media_details': media_details, 'media_details': media_details,
'categories': categories 'categories': categories,
'description_img_url': description_img_url
} }
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
@ -692,7 +832,7 @@ def main():
try: try:
with open(WIKI_PAGES_CSV, 'w', newline='', encoding='utf-8') as f: with open(WIKI_PAGES_CSV, 'w', newline='', encoding='utf-8') as f:
# Basic fields for CSV (detailed content will be in JSON only) # Basic fields for CSV (detailed content will be in JSON only)
fieldnames = ['key', 'language', 'url', 'last_modified', 'sections', 'word_count', 'link_count', 'media_count', 'staleness_score'] fieldnames = ['key', 'language', 'url', 'last_modified', 'sections', 'word_count', 'link_count', 'media_count', 'staleness_score', 'description_img_url']
writer = csv.DictWriter(f, fieldnames=fieldnames) writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader() writer.writeheader()

View file

@ -1,11 +1,40 @@
key,language,url,last_modified,sections,word_count,link_count,media_count,staleness_score key,language,url,last_modified,sections,word_count,link_count,media_count,staleness_score,description_img_url
building,en,https://wiki.openstreetmap.org/wiki/Key:building,2025-06-10,31,3774,627,158,8.91 building,en,https://wiki.openstreetmap.org/wiki/Key:building,2025-06-10,31,3774,627,158,8.91,https://wiki.openstreetmap.org/w/images/thumb/6/61/Emptyhouse.jpg/200px-Emptyhouse.jpg
building,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building,2025-05-22,25,3181,544,155,8.91 building,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building,2025-05-22,25,3181,544,155,8.91,https://wiki.openstreetmap.org/w/images/thumb/6/61/Emptyhouse.jpg/200px-Emptyhouse.jpg
source,en,https://wiki.openstreetmap.org/wiki/Key:source,2025-08-12,27,2752,314,42,113.06 source,en,https://wiki.openstreetmap.org/wiki/Key:source,2025-08-12,27,2752,314,42,113.06,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
source,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source,2024-02-07,23,2593,230,35,113.06 source,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source,2024-02-07,23,2593,230,35,113.06,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
highway,en,https://wiki.openstreetmap.org/wiki/Key:highway,2025-04-10,30,4126,780,314,20.35 highway,en,https://wiki.openstreetmap.org/wiki/Key:highway,2025-04-10,30,4126,780,314,20.35,https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Roads_in_Switzerland_%2827965437018%29.jpg/200px-Roads_in_Switzerland_%2827965437018%29.jpg
highway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:highway,2025-01-05,30,4141,695,313,20.35 highway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:highway,2025-01-05,30,4141,695,313,20.35,https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Roads_in_Switzerland_%2827965437018%29.jpg/200px-Roads_in_Switzerland_%2827965437018%29.jpg
addr:housenumber,en,https://wiki.openstreetmap.org/wiki/Key:addr:housenumber,2025-07-24,11,330,97,20,14.01 addr:housenumber,en,https://wiki.openstreetmap.org/wiki/Key:addr:housenumber,2025-07-24,11,330,97,20,14.01,https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Ferry_Street%2C_Portaferry_%2809%29%2C_October_2009.JPG/200px-Ferry_Street%2C_Portaferry_%2809%29%2C_October_2009.JPG
addr:housenumber,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:housenumber,2025-08-23,15,1653,150,77,14.01 addr:housenumber,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:housenumber,2025-08-23,15,1653,150,77,14.01,https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
addr:street,en,https://wiki.openstreetmap.org/wiki/Key:addr:street,2024-10-29,12,602,101,16,66.04 addr:street,en,https://wiki.openstreetmap.org/wiki/Key:addr:street,2024-10-29,12,602,101,16,66.04,https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/UK_-_London_%2830474933636%29.jpg/200px-UK_-_London_%2830474933636%29.jpg
addr:street,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:street,2025-08-23,15,1653,150,77,66.04 addr:street,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:street,2025-08-23,15,1653,150,77,66.04,https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
addr:city,en,https://wiki.openstreetmap.org/wiki/Key:addr:city,2025-07-29,15,802,105,17,9.93,https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Lillerod.jpg/200px-Lillerod.jpg
addr:city,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:city,2025-08-23,15,1653,150,77,9.93,https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
name,en,https://wiki.openstreetmap.org/wiki/Key:name,2025-07-25,17,2196,281,82,42.39,https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Helena%2C_Montana.jpg/200px-Helena%2C_Montana.jpg
name,fr,https://wiki.openstreetmap.org/wiki/FR:Key:name,2025-01-16,21,1720,187,60,42.39,https://wiki.openstreetmap.org/w/images/3/37/Strakers.jpg
addr:postcode,en,https://wiki.openstreetmap.org/wiki/Key:addr:postcode,2024-10-29,14,382,83,11,67.11,https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Farrer_post_code.jpg/200px-Farrer_post_code.jpg
addr:postcode,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:postcode,2025-08-23,15,1653,150,77,67.11,https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
natural,en,https://wiki.openstreetmap.org/wiki/Key:natural,2025-07-17,17,2070,535,189,22.06,https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/VocaDi-Nature%2CGeneral.jpeg/200px-VocaDi-Nature%2CGeneral.jpeg
natural,fr,https://wiki.openstreetmap.org/wiki/FR:Key:natural,2025-04-21,13,1499,455,174,22.06,https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/VocaDi-Nature%2CGeneral.jpeg/200px-VocaDi-Nature%2CGeneral.jpeg
surface,en,https://wiki.openstreetmap.org/wiki/Key:surface,2025-08-28,24,3475,591,238,264.64,https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/Transportation_in_Tanzania_Traffic_problems.JPG/200px-Transportation_in_Tanzania_Traffic_problems.JPG
surface,fr,https://wiki.openstreetmap.org/wiki/FR:Key:surface,2022-02-22,13,2587,461,232,264.64,https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/Transportation_in_Tanzania_Traffic_problems.JPG/200px-Transportation_in_Tanzania_Traffic_problems.JPG
addr:country,en,https://wiki.openstreetmap.org/wiki/Key:addr:country,2024-12-01,9,184,65,11,22.96,https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Europe_ISO_3166-1.svg/200px-Europe_ISO_3166-1.svg.png
addr:country,fr,https://wiki.openstreetmap.org/wiki/FR:Key:addr:country,2025-03-25,8,187,65,11,22.96,https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Europe_ISO_3166-1.svg/200px-Europe_ISO_3166-1.svg.png
landuse,en,https://wiki.openstreetmap.org/wiki/Key:landuse,2025-03-01,17,2071,446,168,39.41,https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Changing_landuse_-_geograph.org.uk_-_1137810.jpg/200px-Changing_landuse_-_geograph.org.uk_-_1137810.jpg
landuse,fr,https://wiki.openstreetmap.org/wiki/FR:Key:landuse,2024-08-20,19,2053,418,182,39.41,https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Changing_landuse_-_geograph.org.uk_-_1137810.jpg/200px-Changing_landuse_-_geograph.org.uk_-_1137810.jpg
power,en,https://wiki.openstreetmap.org/wiki/Key:power,2025-02-28,20,641,127,21,124.89,https://wiki.openstreetmap.org/w/images/thumb/0/01/Power-tower.JPG/200px-Power-tower.JPG
power,fr,https://wiki.openstreetmap.org/wiki/FR:Key:power,2023-06-27,14,390,105,25,124.89,https://wiki.openstreetmap.org/w/images/thumb/0/01/Power-tower.JPG/200px-Power-tower.JPG
waterway,en,https://wiki.openstreetmap.org/wiki/Key:waterway,2025-03-10,21,1830,365,118,77.94,https://wiki.openstreetmap.org/w/images/thumb/f/fe/450px-Marshall-county-indiana-yellow-river.jpg/200px-450px-Marshall-county-indiana-yellow-river.jpg
waterway,fr,https://wiki.openstreetmap.org/wiki/FR:Key:waterway,2024-03-08,18,1291,272,113,77.94,https://wiki.openstreetmap.org/w/images/thumb/f/fe/450px-Marshall-county-indiana-yellow-river.jpg/200px-450px-Marshall-county-indiana-yellow-river.jpg
building:levels,en,https://wiki.openstreetmap.org/wiki/Key:building:levels,2025-08-13,16,1351,204,25,76.11,https://wiki.openstreetmap.org/w/images/thumb/4/47/Building-levels.png/200px-Building-levels.png
building:levels,fr,https://wiki.openstreetmap.org/wiki/FR:Key:building:levels,2024-08-01,15,1457,202,26,76.11,https://wiki.openstreetmap.org/w/images/thumb/4/47/Building-levels.png/200px-Building-levels.png
amenity,en,https://wiki.openstreetmap.org/wiki/Key:amenity,2025-08-24,29,3066,915,504,160.78,https://wiki.openstreetmap.org/w/images/thumb/a/a5/Mapping-Features-Parking-Lot.png/200px-Mapping-Features-Parking-Lot.png
amenity,fr,https://wiki.openstreetmap.org/wiki/FR:Key:amenity,2023-07-19,22,2146,800,487,160.78,https://wiki.openstreetmap.org/w/images/thumb/a/a5/Mapping-Features-Parking-Lot.png/200px-Mapping-Features-Parking-Lot.png
barrier,en,https://wiki.openstreetmap.org/wiki/Key:barrier,2025-04-15,17,2137,443,173,207.98,https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg/200px-2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg
barrier,fr,https://wiki.openstreetmap.org/wiki/FR:Key:barrier,2022-08-16,15,542,103,18,207.98,https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg/200px-2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg
source:date,en,https://wiki.openstreetmap.org/wiki/Key:source:date,2023-04-01,11,395,75,10,22.47,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
source:date,fr,https://wiki.openstreetmap.org/wiki/FR:Key:source:date,2023-07-21,10,419,75,11,22.47,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
service,en,https://wiki.openstreetmap.org/wiki/Key:service,2025-03-16,22,1436,218,17,83.79,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
service,fr,https://wiki.openstreetmap.org/wiki/FR:Key:service,2024-03-04,11,443,100,10,83.79,https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
addr:state,en,https://wiki.openstreetmap.org/wiki/Key:addr:state,2023-06-23,12,289,74,11,100,https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/WVaCent.jpg/200px-WVaCent.jpg

1 key language url last_modified sections word_count link_count media_count staleness_score description_img_url
2 building en https://wiki.openstreetmap.org/wiki/Key:building 2025-06-10 31 3774 627 158 8.91 https://wiki.openstreetmap.org/w/images/thumb/6/61/Emptyhouse.jpg/200px-Emptyhouse.jpg
3 building fr https://wiki.openstreetmap.org/wiki/FR:Key:building 2025-05-22 25 3181 544 155 8.91 https://wiki.openstreetmap.org/w/images/thumb/6/61/Emptyhouse.jpg/200px-Emptyhouse.jpg
4 source en https://wiki.openstreetmap.org/wiki/Key:source 2025-08-12 27 2752 314 42 113.06 https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
5 source fr https://wiki.openstreetmap.org/wiki/FR:Key:source 2024-02-07 23 2593 230 35 113.06 https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
6 highway en https://wiki.openstreetmap.org/wiki/Key:highway 2025-04-10 30 4126 780 314 20.35 https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Roads_in_Switzerland_%2827965437018%29.jpg/200px-Roads_in_Switzerland_%2827965437018%29.jpg
7 highway fr https://wiki.openstreetmap.org/wiki/FR:Key:highway 2025-01-05 30 4141 695 313 20.35 https://upload.wikimedia.org/wikipedia/commons/thumb/7/78/Roads_in_Switzerland_%2827965437018%29.jpg/200px-Roads_in_Switzerland_%2827965437018%29.jpg
8 addr:housenumber en https://wiki.openstreetmap.org/wiki/Key:addr:housenumber 2025-07-24 11 330 97 20 14.01 https://upload.wikimedia.org/wikipedia/commons/thumb/1/16/Ferry_Street%2C_Portaferry_%2809%29%2C_October_2009.JPG/200px-Ferry_Street%2C_Portaferry_%2809%29%2C_October_2009.JPG
9 addr:housenumber fr https://wiki.openstreetmap.org/wiki/FR:Key:addr:housenumber 2025-08-23 15 1653 150 77 14.01 https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
10 addr:street en https://wiki.openstreetmap.org/wiki/Key:addr:street 2024-10-29 12 602 101 16 66.04 https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/UK_-_London_%2830474933636%29.jpg/200px-UK_-_London_%2830474933636%29.jpg
11 addr:street fr https://wiki.openstreetmap.org/wiki/FR:Key:addr:street 2025-08-23 15 1653 150 77 66.04 https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
12 addr:city en https://wiki.openstreetmap.org/wiki/Key:addr:city 2025-07-29 15 802 105 17 9.93 https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/Lillerod.jpg/200px-Lillerod.jpg
13 addr:city fr https://wiki.openstreetmap.org/wiki/FR:Key:addr:city 2025-08-23 15 1653 150 77 9.93 https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
14 name en https://wiki.openstreetmap.org/wiki/Key:name 2025-07-25 17 2196 281 82 42.39 https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Helena%2C_Montana.jpg/200px-Helena%2C_Montana.jpg
15 name fr https://wiki.openstreetmap.org/wiki/FR:Key:name 2025-01-16 21 1720 187 60 42.39 https://wiki.openstreetmap.org/w/images/3/37/Strakers.jpg
16 addr:postcode en https://wiki.openstreetmap.org/wiki/Key:addr:postcode 2024-10-29 14 382 83 11 67.11 https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Farrer_post_code.jpg/200px-Farrer_post_code.jpg
17 addr:postcode fr https://wiki.openstreetmap.org/wiki/FR:Key:addr:postcode 2025-08-23 15 1653 150 77 67.11 https://wiki.openstreetmap.org/w/images/thumb/e/e9/Housenumber-karlsruhe-de.png/200px-Housenumber-karlsruhe-de.png
18 natural en https://wiki.openstreetmap.org/wiki/Key:natural 2025-07-17 17 2070 535 189 22.06 https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/VocaDi-Nature%2CGeneral.jpeg/200px-VocaDi-Nature%2CGeneral.jpeg
19 natural fr https://wiki.openstreetmap.org/wiki/FR:Key:natural 2025-04-21 13 1499 455 174 22.06 https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/VocaDi-Nature%2CGeneral.jpeg/200px-VocaDi-Nature%2CGeneral.jpeg
20 surface en https://wiki.openstreetmap.org/wiki/Key:surface 2025-08-28 24 3475 591 238 264.64 https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/Transportation_in_Tanzania_Traffic_problems.JPG/200px-Transportation_in_Tanzania_Traffic_problems.JPG
21 surface fr https://wiki.openstreetmap.org/wiki/FR:Key:surface 2022-02-22 13 2587 461 232 264.64 https://upload.wikimedia.org/wikipedia/commons/thumb/a/a2/Transportation_in_Tanzania_Traffic_problems.JPG/200px-Transportation_in_Tanzania_Traffic_problems.JPG
22 addr:country en https://wiki.openstreetmap.org/wiki/Key:addr:country 2024-12-01 9 184 65 11 22.96 https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Europe_ISO_3166-1.svg/200px-Europe_ISO_3166-1.svg.png
23 addr:country fr https://wiki.openstreetmap.org/wiki/FR:Key:addr:country 2025-03-25 8 187 65 11 22.96 https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Europe_ISO_3166-1.svg/200px-Europe_ISO_3166-1.svg.png
24 landuse en https://wiki.openstreetmap.org/wiki/Key:landuse 2025-03-01 17 2071 446 168 39.41 https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Changing_landuse_-_geograph.org.uk_-_1137810.jpg/200px-Changing_landuse_-_geograph.org.uk_-_1137810.jpg
25 landuse fr https://wiki.openstreetmap.org/wiki/FR:Key:landuse 2024-08-20 19 2053 418 182 39.41 https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Changing_landuse_-_geograph.org.uk_-_1137810.jpg/200px-Changing_landuse_-_geograph.org.uk_-_1137810.jpg
26 power en https://wiki.openstreetmap.org/wiki/Key:power 2025-02-28 20 641 127 21 124.89 https://wiki.openstreetmap.org/w/images/thumb/0/01/Power-tower.JPG/200px-Power-tower.JPG
27 power fr https://wiki.openstreetmap.org/wiki/FR:Key:power 2023-06-27 14 390 105 25 124.89 https://wiki.openstreetmap.org/w/images/thumb/0/01/Power-tower.JPG/200px-Power-tower.JPG
28 waterway en https://wiki.openstreetmap.org/wiki/Key:waterway 2025-03-10 21 1830 365 118 77.94 https://wiki.openstreetmap.org/w/images/thumb/f/fe/450px-Marshall-county-indiana-yellow-river.jpg/200px-450px-Marshall-county-indiana-yellow-river.jpg
29 waterway fr https://wiki.openstreetmap.org/wiki/FR:Key:waterway 2024-03-08 18 1291 272 113 77.94 https://wiki.openstreetmap.org/w/images/thumb/f/fe/450px-Marshall-county-indiana-yellow-river.jpg/200px-450px-Marshall-county-indiana-yellow-river.jpg
30 building:levels en https://wiki.openstreetmap.org/wiki/Key:building:levels 2025-08-13 16 1351 204 25 76.11 https://wiki.openstreetmap.org/w/images/thumb/4/47/Building-levels.png/200px-Building-levels.png
31 building:levels fr https://wiki.openstreetmap.org/wiki/FR:Key:building:levels 2024-08-01 15 1457 202 26 76.11 https://wiki.openstreetmap.org/w/images/thumb/4/47/Building-levels.png/200px-Building-levels.png
32 amenity en https://wiki.openstreetmap.org/wiki/Key:amenity 2025-08-24 29 3066 915 504 160.78 https://wiki.openstreetmap.org/w/images/thumb/a/a5/Mapping-Features-Parking-Lot.png/200px-Mapping-Features-Parking-Lot.png
33 amenity fr https://wiki.openstreetmap.org/wiki/FR:Key:amenity 2023-07-19 22 2146 800 487 160.78 https://wiki.openstreetmap.org/w/images/thumb/a/a5/Mapping-Features-Parking-Lot.png/200px-Mapping-Features-Parking-Lot.png
34 barrier en https://wiki.openstreetmap.org/wiki/Key:barrier 2025-04-15 17 2137 443 173 207.98 https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg/200px-2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg
35 barrier fr https://wiki.openstreetmap.org/wiki/FR:Key:barrier 2022-08-16 15 542 103 18 207.98 https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg/200px-2014_Bystrzyca_K%C5%82odzka%2C_mury_obronne_05.jpg
36 source:date en https://wiki.openstreetmap.org/wiki/Key:source:date 2023-04-01 11 395 75 10 22.47 https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
37 source:date fr https://wiki.openstreetmap.org/wiki/FR:Key:source:date 2023-07-21 10 419 75 11 22.47 https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
38 service en https://wiki.openstreetmap.org/wiki/Key:service 2025-03-16 22 1436 218 17 83.79 https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
39 service fr https://wiki.openstreetmap.org/wiki/FR:Key:service 2024-03-04 11 443 100 10 83.79 https://wiki.openstreetmap.org/w/images/thumb/7/76/Osm_element_node.svg/30px-Osm_element_node.svg.png
40 addr:state en https://wiki.openstreetmap.org/wiki/Key:addr:state 2023-06-23 12 289 74 11 100 https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/WVaCent.jpg/200px-WVaCent.jpg