feat(ET-009): activate EnduroRussia + Wikiloc GPS sources
Конфиг-only активация двух новых источников GPS-треков поверх pipeline ET-008. Не вводит новых компонентов, БД-таблиц, endpoint'ов. Config: - config/gps_sources.yaml: enduro_russia enabled=true, base_url исправлен на endurorussia.ru (без дефиса); добавлена запись wikiloc с max_tracks_per_run=50, activity_filter=[motorcycle, enduro]. - config/gps_regions.yaml: wikiloc добавлен в tsfo_plus_chuvashia.sources. Parser: - wikiloc.py: добавлен soft-cap max_tracks_per_run в collect(), извлечение created_at из GPX metadata/первого trkpt — для корректной межисточниковой дедупликации с EnduroRussia. UI (src/web/gps_tracks.js): - GPS_SOURCE_COLORS: добавлен цвет wikiloc (#4363d8). - Дефолтный фильтр sources включает wikiloc. - GPS_SOURCE_ATTRIBUTIONS: маппинг source_id → строка атрибуции; _updateGpsAttribution() подтягивает /api/gps-tracks/health и выставляет attribution с теми источниками, у которых tracks > 0. - _buildGpsFiltersUI: чекбокс «Wikiloc» в #gps-source-grid. Tests: - Fixtures: 7 файлов в tests/fixtures/gps-tracks/. - Unit: 10 UT-ER + 10 UT-WL — парсеры, MAPPING, bbox-фильтр, pagination, 429/403 graceful-stop, rate-limit, max_tracks_per_run. - Integration: IT-ER-01, IT-WL-01, IT-WL-02, IT-DEDUP-01, IT-LIC-01 через scripts.gps_collect.main + httpx.MockTransport. - Contract: 2 CT-ER с маркером @pytest.mark.network (nightly only). - JS: 2 новых теста на наличие wikiloc в SOURCE_COLORS и в фильтрах. Linters/Tests: ruff clean (новые файлы), 166 pytest passed, 24 JS-tests passed. Refs: ET-009 Acceptance: AC-01..AC-08, AC-14..AC-17 (для AC-09..AC-13 — продакшн-прогон) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,12 +10,23 @@ const GPS_TRACKS_MIN_ZOOM = 8; // ниже — слой скрыт
|
||||
const GPS_SOURCE_COLORS = {
|
||||
osm: '#3cb44b',
|
||||
enduro_russia: '#e6194b',
|
||||
ttrails: '#4363d8',
|
||||
wikiloc: '#4363d8',
|
||||
ttrails: '#911eb4',
|
||||
offmaps: '#f58231',
|
||||
nakarte: '#911eb4',
|
||||
nakarte: '#f032e6',
|
||||
};
|
||||
const GPS_FALLBACK_COLORS = ['#42d4f4', '#f032e6', '#bfef45', '#fabed4', '#469990', '#dcbeff', '#9A6324', '#fffac8'];
|
||||
|
||||
// ET-009: атрибуция для каждого источника. Используется при сборке
|
||||
// MapLibre attribution control: к строке source-attribution добавляются
|
||||
// все источники, у которых tracks_by_source > 0.
|
||||
const GPS_SOURCE_ATTRIBUTIONS = {
|
||||
osm: '© OpenStreetMap contributors (ODbL)',
|
||||
enduro_russia: 'EnduroRussia.ru',
|
||||
wikiloc: '© Wikiloc contributors',
|
||||
ttrails: 'ttrails.ru',
|
||||
};
|
||||
|
||||
const GPS_ACTIVITY_COLORS = {
|
||||
enduro: '#e6194b',
|
||||
moto: '#f58231',
|
||||
@@ -52,7 +63,7 @@ window.gpsTracksLayer = {
|
||||
enabled: false,
|
||||
filters: {
|
||||
activities: ['enduro', 'moto', 'offroad', 'bicycle', 'hike', 'ski', 'other'],
|
||||
sources: ['osm', 'enduro_russia', 'ttrails'],
|
||||
sources: ['osm', 'enduro_russia', 'wikiloc', 'ttrails'],
|
||||
colorMode: 'source'
|
||||
},
|
||||
sourceId: 'gps-tracks-tiles',
|
||||
@@ -188,6 +199,44 @@ function _ensureGpsLayers(map) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ET-009: динамически обновляет attribution источника `gps-tracks-tiles` на
|
||||
* основе ответа /api/gps-tracks/health.tracks_by_source. Включает в строку
|
||||
* атрибуцию каждого источника, у которого > 0 треков в БД. Падение запроса —
|
||||
* не блокирующее: остаётся последняя установленная атрибуция.
|
||||
*/
|
||||
async function _updateGpsAttribution(map) {
|
||||
if (!map || !map.getSource) return;
|
||||
const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || '';
|
||||
try {
|
||||
const resp = await fetch(`${basePath}/api/gps-tracks/health`);
|
||||
if (!resp.ok) return;
|
||||
const data = await resp.json();
|
||||
const counts = data && data.tracks_by_source ? data.tracks_by_source : {};
|
||||
const labels = [];
|
||||
for (const src of Object.keys(GPS_SOURCE_ATTRIBUTIONS)) {
|
||||
if (counts[src] && counts[src] > 0) {
|
||||
labels.push(GPS_SOURCE_ATTRIBUTIONS[src]);
|
||||
}
|
||||
}
|
||||
if (labels.length === 0) {
|
||||
labels.push(GPS_SOURCE_ATTRIBUTIONS.osm);
|
||||
}
|
||||
const attribution = labels.join(', ');
|
||||
const src = map.getSource(window.gpsTracksLayer.sourceId);
|
||||
if (src && typeof src.attribution !== 'undefined') {
|
||||
src.attribution = attribution;
|
||||
}
|
||||
// MapLibre не перечитывает source.attribution автоматически —
|
||||
// дергаем resize чтобы обновить AttributionControl
|
||||
if (typeof map._controls !== 'undefined') {
|
||||
try { map.resize(); } catch (_) { /* noop */ }
|
||||
}
|
||||
} catch (_) {
|
||||
// network failure — оставляем дефолтную атрибуцию
|
||||
}
|
||||
}
|
||||
|
||||
function _findGpsInsertPosition(map) {
|
||||
/**
|
||||
* Returns the id of the first layer that GPS tracks should be inserted
|
||||
@@ -411,6 +460,8 @@ function onPublicTracksCheckbox() {
|
||||
_ensureGpsSources(map);
|
||||
_ensureGpsLayers(map);
|
||||
_setupGpsClickHandler(map);
|
||||
// ET-009: подтянуть актуальные атрибуции (osm, enduro_russia, wikiloc)
|
||||
_updateGpsAttribution(map);
|
||||
|
||||
// Убедиться, что moveend listener есть
|
||||
map.off('moveend', onGpsMapMoveEnd);
|
||||
@@ -476,8 +527,13 @@ function _buildGpsFiltersUI() {
|
||||
// Источники (из localStorage или дефолт)
|
||||
const srcGrid = document.getElementById('gps-source-grid');
|
||||
if (srcGrid) {
|
||||
const allSources = ['osm', 'enduro_russia', 'ttrails'];
|
||||
const sourceLabels = { osm: 'OSM', enduro_russia: 'EnduroRussia.ru', ttrails: 'Тропинки.ру' };
|
||||
const allSources = ['osm', 'enduro_russia', 'wikiloc', 'ttrails'];
|
||||
const sourceLabels = {
|
||||
osm: 'OSM',
|
||||
enduro_russia: 'EnduroRussia',
|
||||
wikiloc: 'Wikiloc',
|
||||
ttrails: 'Тропинки.ру',
|
||||
};
|
||||
srcGrid.innerHTML = allSources.map(src => {
|
||||
const checked = window.gpsTracksLayer.filters.sources.includes(src);
|
||||
return `
|
||||
|
||||
Reference in New Issue
Block a user