Йдуть роботи над кодом програми
Зараз оновлюємо сервіс. Деякі функції можуть працювати некоректно або тимчасово бути недоступними. Дякуємо за терпіння.

sidc, observation_datetime, y, x), «library» та подібні довідники. У зведенні імпорту зазначено, який аркуш пропущено і чому. Якщо в книзі лише службовий аркуш, він читається як і раніше. Перемикач «Читати всі аркуші книги» охоплює звичайні аркуші («21.08» + «бпла»).files/circle.png), підпис точки — опис цілі, в описі три рядки: Координати, Локація, Опис. Тип, MGRS і назва файлу у вигрузку не потрапляють.0,2416….opsServiceSheetReason(sh) в importSingleOpsFile: аркуш пропускається за назвою (delta|дельта|kropyva|library|довідник|справочник|lookup|словник|dict|meta) або за шапкою — sidc/observation_datetime в перших 5 рядках. Фільтр іде до #xlsxAllSheets, тому з вимкненим перемикачем береться перший звичайний аркуш, а не службовий. Якщо службовими виявились усі аркуші — фільтр скасовується (книга-вигрузка імпортується як є). skippedSheets тепер містить рядки «назва — причина» і показується і в одиничному зведенні, і в пакетному («(без «Delta», «library»)»). Перевірено sheettest.js (8 перевірок, книги збираються через XLSX.write): звичайний список + Delta + library, дві звичайні сторінки, книга лише з Delta, вимкнений перемикач, те саме в «Конвертації». dedupetest.js на справжніх файлах: 877 → 531 схлопнуто → 346 точок, 346 координат, 0 втрачених, 0 підписів зі службового аркуша.opsNameCoreMap(keys) — одна реалізація для карти й конвертації: ключі opsNormNameKey сортуються за кількістю слів, кожен зливається в перше «ядро», чий набір слів є його підмножиною (порожня назва не зливається). У opsNormalizeMarkers групи byName зводяться в byCore (ядро йде першим — його підпис стає назвою точки), OPS_CITY_COLLAPSE_MIN тепер рахується по byCore.size, тому варіанти написання більше не роблять точку «≈ місто»; для злитих ключів у __opsMergeIndex додано алиаси, щоб попапи знаходились за будь-яким варіантом підпису. У convert.js dedupe() групує по координаті, бере deps.opsNameCoreMap і залишає рядок із підписом-ядром, зводячи в нього dupes і перелік файлів; лічильники statRead/statDupes зберігаються в coords_convert_v1 (read, dupes). Новий opsDedupNote() у зведенні імпорту. Перевірено на справжніх 5 файлах користувача (dedupetest.js, 25 перевірок): 1008 рядків → 660 схлопнуто → 348 точок, 346 координат без втрат і без зайвого, лишились рівно 2 координати зі справді різними цілями у файлах. Cache-bust: convert.js?v=8, convert.css?v=6.__coordsConvertDeps.parseText в index.html робить те саме, що обробник #parseTextBtn — expandGoogleMapsShortLinksInText → collectMarkersFromText → textImportHeaders, але віддає масив точок, а не кладе їх у набір карти (тому addMarker/снапшоти не зачеплені). У convert.js — parseText(): точки через sinkRow з джерелом textSourceName(), потім dedupe, і той самий histUpload з File([raw], name, {type:'text/plain'}). Подія журналу convert_text (chars / found / sample / headers). Перевірено збігом з оперативним розбором на одному тексті: 6 точок, ті самі координати й підписи. Cache-bust: convert.js?v=7, convert.css?v=5.convert_history.php — дії list / add / file / points / del. Сховище logs/convert/{Y-m-d}/{id}.json|.src|.pts: опис, оригінал файлу і точки в gzip. Дата зашита в id (Ymd-10hex), тому читання не сканує каталоги. Доступ — share_api_access('editor') (гості посилань не бачать нічого), del — лише coords_admin_ok. Ліміти: 25 МБ на файл, 60 тис. точок на запис (truncated у відповіді), 1000 записів на добу, RateLimiter на кожну дію. На 12 463 точках: запис 1,07 с, .pts 158 КБ, читання 30 мс.post_max_size PHP викидає цілком — $_POST/$_FILES порожні, і запит провалювався б у безпечну list, а клієнт вважав би файл записаним. Додано окрему перевірку → 413. Content-Disposition: ASCII-запас у filename= + справжня назва в filename*=UTF-8.convert.js: histUpload після імпорту віддає кожен файл окремо разом з його точками (групування по r.source, яке ставить парсер). histGroupLocal перегруповує добу UTC у місцеву дату по ts. Показ запису на карті йде через спільний showMapFor + renderScene, рядки будуються makeRow без запису в набір таблиці; backFromMap вертає туди, звідки прийшли (mapFrom). Панель — #convHistPane у grid-area:map, body.conv-hist.cleanup.php: чистка logs/convert старше HIST_KEEP_DAYS=90 — вік беруть із назви каталогу (дати), бо mtime міняється від кожного нового файлу того ж дня. Нові події журналу: convert_hist_open, convert_hist_table, convert_hist_map, convert_hist_get, convert_hist_del. Cache-bust: convert.js?v=6, convert.css?v=4.convert.js?v=5.renderClusteredMarkers у opsRenderMarkerScene(objects, layerGroup, state): кластеризація по сітці, відсів по getBounds().pad(OPS_VIEW_PAD), діф шарів через state.shown і кеш груп у state.cache. Оперативна карта ходить тим самим кодом (стан _opsScene), «Конвертація» — через __coordsConvertDeps.renderScene зі своїм L.layerGroup. Рядки таблиці отримали поля, які читає відмальовка (taxGroup, taxIcon, colCategory, sourceName), плюс noMergeInfo — блок «зустрічається у N джерелах» тримається на індексі оперативного набору, тож для чужого набору не додається. Перемальовка перегляду — на moveend/zoomend. На 12 463 точках у DOM 183–277 шарів.fitBounds на вироджених межах (одна точка) викручував зум до максимуму, а getBounds() після перерахунку пікселів у градуси давав північ ≈ південь з похибкою ~1e-7 — точка не проходила відсів. Тепер для нульових межів setView(center, 14), у fitBounds додано maxZoom: 16, а межі відсіву розширені на 1e-6.tbody рендериться вікно видимих рядків плюс запас (ROW_OVERSCAN=12), а висоту прокрутки добирають дві порожні розпірки-tr (first*rowH і (len-last)*rowH). Висота рядка міряється з факту після першої відмальовки, тому масштаб і шрифт пристрою не збивають позиціювання. table-layout:fixed з ширинами по th — ширини колонок не перераховуються при кожному вікні. Обробник scroll викликає renderWindow напряму: браузер і так зводить події до однієї на кадр, а requestAnimationFrame лише додавав кадр затримки. Перевірено на 12 463 рядках: у DOM 48 рядків, висота прокрутки 373 918 px, 20 кроків прокрутки — 320 мс.convert.js: у обробнику change поля файлів ev.target.value = '' стояв перед await importFiles(files) — скидання value обнуляє й сам FileList, тому в розбір ішов порожній список (тести цього не бачили, бо викликали __coordsConvertApi.importFiles напряму). Тепер Array.from(ev.target.files) до очищення; у набір тестів додано шлях через реальне поле вибору (DataTransfer + dispatchEvent) і кліки по всіх кнопках розділу.<?xml version='1.0' encoding='utf-8'?>, CRLF по всьому файлу без завершального переводу рядка, єдиний <Style id="perun_red_ring"> з hotSpot по центру, теки верхня/рік/місяць/день, у Placemark порядок name → description → Point → styleUrl, координати lon,lat без висоти. KMZ = <назва>.kml + files/circle.png (іконка вшита base64, createFolders:false, щоб у архіві не було запису теки). Перевірено порівнянням з еталонним KMZ на 4 764 точках: стиль, дерево тек, порядок тегів і каркас опису збігаються.splitLabel() ділить склеєний підпис: сегмент, який ловить CoordsTaxonomy.canonicalOblast (або /обл|областʼ/), йде в «Локація», решта — в name і «Опис»; фолбек — область із координатної сітки. Поля #convExportTop і #convExportDate задають теки; місяць — українською з MONTHS_UK.detectTableColumns: до розпізнавання адреси додано локац — заголовок «Локація» не підпадав ні під idx.address, ні під idx.label, тому колонка випадала з composeVuLabelParts. Force update: __COORDS_BUILD=2.6.18, SW coords-v2.6.18, app_version.php=2.6.18.js/convert.js + css/convert.css, режим body.mode-convert (таблиця — #convertPane у grid-area:map, карта в цей час visibility:hidden, щоб Leaflet не втрачав розмір). Пагінація по 100 рядків, у DOM — лише сторінка.addMarker на початку віддає точку в window.__coordsMarkerSink, якщо він стоїть. Обгортка __coordsConvertDeps.importFiles ставить приймач, глушить clearBefore і scheduleSnapshotSave на час імпорту — набір оперативної карти й снапшоти не зачіпаються. Дедуп — тими самими opsCoordKey/opsNormNameKey.Folder на область і Style на групу таксономії (IconStyle/color у порядку aabbggrr), KMZ — JSZip з doc.kml. Перегляд на карті — L.circleMarker на спільному canvas-рендерері. Збереження — localStorage: coords_convert_v1 (компактні масиви, таксономія перераховується при завантаженні; 12,5 тис. рядків ≈ 1,2 МБ).detectTableColumns: кириличні варіанти назви колонки перевіряються без \b — межа слова в JS рахує лише латиницю, тому назв\b не збігалось із «Назва цілі» і idx.label лишався -1, а composeVuLabelParts текст цієї колонки не брав узагалі.convert_open, convert_import, convert_map_view, convert_export, convert_clear (підписи в log_labels.php). Force update: __COORDS_BUILD=2.6.17, SW coords-v2.6.17, app_version.php=2.6.17.clusterMarkerObjects: сітка з кроком OPS_CLUSTER_RADIUS_PX (Map клітинок + Float64Array/Uint8Array) замість перебору всіх пар. На 12,5 тис. точок 49 млн порівнянь → ~15 тис.; 565 мс → 32 мс.renderClusteredMarkers: без clearLayers() — _opsShownLayers (ключ групи → шар) і додавання/зняття лише різниці; відсів по map.getBounds().pad(OPS_VIEW_PAD=0.3); кеш груп _opsClusterCache за {zoom, visible.length} + opsInvalidateClusters() у addMarker/видаленні/clearAll/opsNormalizeMarkers. Зум 12: 4452 шари в DOM → 295; блокування потоку за жест 2966 мс → 51 мс.opsMarkerLayer(obj) — L.marker створюється при показі; addMarker кладе leaflet: null. Heap на наборі 12,5 тис.: 53 МБ → 37 МБ. Попап тепер bindPopup (не lazy-клік), тому openMarkerPopup працює зі списку.scheduleRenderClusteredMarkers на requestAnimationFrame (серія zoomend = одна перемальовка) + підписка на moveend. fitToMarkers рахує межі з markers[], бо в шарі лежить лише видима частина. Легенда через opsSyncLegend() — тільки на зміну складу набору.taxonomy.js: stripStrikeMeans більше не робить new RegExp(re.source, re.flags) у циклах (16 регексів на кожну точку). Класифікація набору 1115 → 995 мс, звірено на 12 463 реальних підписах — 0 розбіжностей. Cache-bust: taxonomy.js?v=4.__COORDS_BUILD=2.6.16, SW coords-v2.6.16, app_version.php=2.6.16.opsNormalizeMarkers() у finalizeBatchLoad: групування по opsCoordKey (lat/lon 5 знаків) + opsNormNameKey (сортований набір токенів, без лапок/дефісів/порядку). Мутує markers[], будує __opsMergeIndex.OPS_CITY_COLLAPSE_MIN=3 різних обʼєктів на 1 координату → approx=true (міська привязка), сірі opsPinIconApprox/opsClusterIconApprox._opsMarkerExtraHtml (джерела + ≈); панель: renderNoCoordPanel/__opsNoCoord.parseCoordCell (MGRS через parseMGRSCell, далі findCoordsInText — лише однозначний результат), rowCoordCells, isWideCoordTable (умова: є рядок із ≥2 координатами і таких рядків не менше, ніж рядків з однією; шапка з WGS/MGRS/УРС вимикає режим), collectMarkersFromWideTable — підпис із нечислових клітинок рядка + · k/n, дедуп точок у межах рядка за toFixed(6).detectTableLayout → idx.wide + startRow=0; поза wide-режимом рядок із координатою більше не з'їдається як шапка. collectMarkersFromTable і ...ForAnalytics гілкуються по idx.wide.sheetRowsFromWorksheet (два проходи sheet_to_json: raw:true + raw:false — форматований текст лише для числових клітинок часу/дати), sheetsFromArrayBuffer (усі непорожні аркуші). sheetRowsFromArrayBuffer лишився як обгортка.handleTable({sheet}) — назва аркуша в idx.sheet; у wide-режимі #xlsxColPicker ховається. Перемикач #xlsxAllSheets (localStorage: coords_xlsx_all_sheets), звіт sheetInfo/skippedSheets у showOpsImportSummary.js/analytics.js більше не читає книгу власним кодом — бере deps.sheetsFromArrayBuffer і проходить усі аркуші. Cache-bust: analytics.js?v=8.#viewBrandCredit у #viewBrand (11 px / 10 px у компактному), стилі в index.html + body.view-only у redesign-v3.css. Cache-bust: redesign-v3.css?v=10.normalizeMgrsWhitespace переписано: замість одного жадібного replace (де [ \t\r\n]*\d перетягував «36» наступного рядка) — glueMgrsInLine (ручний скан, стоп на 10 цифрах і на будь-якому не-цифровому символі) + joinSplitMgrsLines (перенос лікуємо лише коли наступний рядок — чисті цифри і сума парна, ≤10).findCoordsInText: [ \t]*(\d{4,10}) замість \s*(\d(?:[\d\s]*\d)?) — цифри лише в межах рядка, без «поглинання» сусідньої координати.stripCoordTokens + блок B у collectMarkersFromText: mainHeader / subHeader (регекс серій ^\d{1,2}\s*(?:[\)\].:-]|сер|черг|хвил|захід), ≤24 симв.), нумерація #n за однаковим підписом і лише для реально доданих точок (push тепер повертає boolean).maskNonCoordNumbers (дати dd.mm.yy(yy), відстані км/km/м/мм/см) перед десятковою гілкою; вільне парування чисел вимкнено для рядків з MGRS або DMS.geocodeName / geocodeNameNominatim / geocodeNamePhoton / geocodeNameGoogle, geocodeObjectsFromText, extractObjectNames, buildRetryQuery, buildAndRenderUnresolvedPlaceClusters + unresolvedPlaceClusters, renderNominatimDebug, словники MANUAL_OBJECTS / EXTERNAL_OBJECTS (normalizeKey, findManualObject, findExternalObject, loadExternalObjects), looksLikeCoordsString, GOOGLE_API_KEY, статуси NOMINATIM_STATUS/PHOTON_STATUS + updateGeoStatusUI (≈650 рядків JS).#importSec4 (#objectsInput, #objectsBtn, #objectsStatus, #debugToggleBtn), панель #nominatimDebug (+ мобільний блок і класи .badge-ok/.badge-fail/.badge-cache), #geoStatus; правила #importSec4 прибрано з css/redesign-v3.css. Файл external_objects.json видалено (він і так був закритий у .htaccess — fetch завжди падав у 403), правило звужено до sessions.json.clearAll() більше не чистить debug-панель і кластери; у «Парсити текст» знято доданок statsByNames (алерт → «Координати не знайдені»). Мітки старих подій n_start/n_done у log_labels.php лишились, щоб історичні логи читались.taxonomy.js: STRIKE_CONTEXT_RE + detectStrikeMeans — «на ураження / засіб ураження» + список засобів (бпла|ствольна арта|каб|…). stripStrikeMeans ріже цей фрагмент перед classify. Cache-bust: taxonomy.js?v=3, analytics.js?v=9.parse_text_* / Word: sample до 4000 символів + масив headers (рядки-шапки без MGRS) — щоб наступні формулювання засобів брати з реальних завантажень, а не вигадувати універсальний парсер шапок.__COORDS_BUILD=2.6.8, SW coords-v2.6.8, app_version.php=2.6.8 (до 2.6.4 — 2.6.0).maps.app.goo.gl (розвідзведення з Google Maps)css/redesign-v3.css, Auth V3 split, mode tabs у header, js/map-chrome.jsOPS_MARKER_FILL/STROKE, opsPinIcon (іконка типу), opsClusterIcon завжди fa-crosshairsOPS_CLUSTER_RADIUS_PX=18; колір типу більше не на кластеріcircleMarker + showAppLoader/hideAppLoader на імпорті (js/analytics.js)</script> після SW ламав блок toast/LBZ — код рендерився як текстapp_version.php + window.__COORDS_BUILD, SW coords-v2.5.1 network-first для HTML/JS/CSSCache-Control: no-store для /, index.html, sw.jsredesign-v3.css?v=9, analytics.js?v=5, map-chrome.js?v=3, taxonomy.js?v=2redesign-preview/red-icons-mockup.html, cluster-zoom-mockup.htmlsave.php (link_wm_generate), окремо від meta.noteicon-*-v24.png + manifest.id для скидання кешу ярликів Windowscoords_dt() / Europe/Kyiv у config.phpcoords-v2.4.1action=save_note), превʼю сітки WMip_history.php / data/ip_nicks.json, клік по IP у журналіadmin_status.php → body.is-adminadmin_settings.php (гейт адмін-паролем)admin_logs.php), «Хто онлайн»is_admin, окремий admin_login.phpanalytics_save/load, lbz_save/load — auth, rate-limit, ETagarchive.php?preview_link=)/data/ /logs/, owner_key у save.phpCOORDS_ADMIN_PASS, COORDS_ACCESS_CODE, COORDS_LOG_SECRETexpires_at, швидкий tail логів, ліміти connections
sidc, «library»): у них ті самі цілі в машинному вигляді, від них лише подвоєння.Завантажте файли за місяць або квартал — сервіс покаже зведення: топ населених пунктів, розподіл по областях, частоту згадок обʼєктів (АЗС, заводи тощо) та динаміку по датах.
Дистанційні мінування за координатами з коротким відео. Обовʼязково прикріпіть відео у браузері (перетягніть або «оберіть відео») — шлях на компʼютері (C:\…) сам не передається. З назви файлу на кшталт «36T YT 17611 80072 Мінування.mp4» підтягнуться координата й опис.
Координати з файлів ідуть у таблицю, а не на карту. Відберіть області й типи цілей, перевірте відібране на карті та вигрузіть у KML / KMZ для Google Earth.
Маркери відсутні
Імпортуйте файл або введіть координати вручну
Програма знайшла слово, яке не розпізнала. Категорія — це тип самого обʼєкта (склад, РЛС, ТЕЦ), а не засіб удару (ФАБ, КАБ, УМПК, БЕК).
Файл робиться за вашим зразком: теки верхня → рік → місяць → день, усі точки одним червоним кільцем, у описі — координати, локація й опис цілі.
Зараз оновлюємо сервіс. Деякі функції можуть працювати некоректно або тимчасово бути недоступними. Дякуємо за терпіння.