From 5c5404b1226c1765c5fe6284ccff4ca486555940 Mon Sep 17 00:00:00 2001
From: Azidaan <101521579+Azidaan@users.noreply.github.com>
Date: Thu, 16 Apr 2026 15:26:59 +0300
Subject: [PATCH] Initial commit: v1.0
---
.claude/settings.json | 5 +
.gitignore | 2 +
README.md | 145 ++
UAV-GeoLoc_Dataset_Analysis.md | 688 +++++
app.js | 217 ++
capture.html | 908 +++++++
graphify-out/GRAPH_REPORT.md | 86 +
...56d6da97a788db146a4f6f81e57e9cc29ab68.json | 1 +
...df25c352f2c6709ad3bf1285f09bc3a5648f8.json | 1 +
...12d0cde26e4f5b7486db698de1332c03510de.json | 1 +
graphify-out/cost.json | 12 +
graphify-out/graph.html | 257 ++
graphify-out/graph.json | 2319 +++++++++++++++++
graphify-out/manifest.json | 7 +
index.html | 224 ++
mapview.html | 330 +++
package-lock.json | 854 ++++++
package.json | 21 +
server.js | 503 ++++
settings.json | 23 +
start.bat | 57 +
style.css | 530 ++++
ui.js | 373 +++
23 files changed, 7564 insertions(+)
create mode 100644 .claude/settings.json
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 UAV-GeoLoc_Dataset_Analysis.md
create mode 100644 app.js
create mode 100644 capture.html
create mode 100644 graphify-out/GRAPH_REPORT.md
create mode 100644 graphify-out/cache/22c703cdee528e9e63d53a961b656d6da97a788db146a4f6f81e57e9cc29ab68.json
create mode 100644 graphify-out/cache/65ff875203817b898ff241da68adf25c352f2c6709ad3bf1285f09bc3a5648f8.json
create mode 100644 graphify-out/cache/d9926deab971545bafef0edc95312d0cde26e4f5b7486db698de1332c03510de.json
create mode 100644 graphify-out/cost.json
create mode 100644 graphify-out/graph.html
create mode 100644 graphify-out/graph.json
create mode 100644 graphify-out/manifest.json
create mode 100644 index.html
create mode 100644 mapview.html
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 server.js
create mode 100644 settings.json
create mode 100644 start.bat
create mode 100644 style.css
create mode 100644 ui.js
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..70b97cd
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,5 @@
+{
+ "enabledPlugins": {
+ "superpowers@superpowers-dev": true
+ }
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fe03b2b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+output
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9798ac7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,145 @@
+# SandCastle
+
+Инструмент для массового захвата Google Photorealistic 3D Tiles в формате датасета **UAV-GeoLoc**. Через Cesium снимает ортофото-кропы сцены (DB) и «дроновые» кадры (query) по сетке, формирует JSON траектории камеры и сохраняет всё в дерево папок, совместимое с UAV-GeoLoc.
+
+## Быстрый старт
+
+1. Установите Node.js 14+.
+2. Запустите `start.bat`:
+ - `start.bat` — запуск на порту `3000`
+ - `start.bat 3001` — запуск на указанном порту
+3. Откройте `http://localhost:<порт>` если браузер не открылся сам.
+
+## Рабочий процесс
+
+1. **Панель управления** ([index.html](index.html)) — задайте имена сцены и параметры:
+ - `Dataset / Country / City / Region` — 4 уровня дерева в `output/`
+ - `Точка 1 / Точка 2` — противоположные углы прямоугольника в формате DMS
+ - `Шаг сетки (метры)` — размер одного тайла
+ - `Прореживание` — 1 = все точки, 2 = каждая вторая
+ - `DB crop sizes` — ровно 11 уникальных размеров PNG-кропов (через запятую)
+ - `Разрешение query кадра` — размер итогового JPEG (по умолчанию 512)
+ - `FOV вертикальный` — параметр съёмки query-камеры
+ - `Pitch query` — фиксирован в `-90` (top-down)
+ - `Высоты (3 слота)` — чекбоксы + редактируемые значения высот
+ - `Абсолютная высота` — выкл = над рельефом
+ - `Предзагрузка тайлов` — прогрев Cesium перед съёмкой
+ - `Пропускать пустые зоны` — не сохранять области без 3D-контента
+2. **Сохранить настройки** → `settings.json`.
+3. **Создать папки** → дерево сцены и `{region}.txt` с углами прямоугольника.
+4. **Запустить захват** — откроется два окна:
+ - **Карта зоны** ([mapview.html](mapview.html)) — чистый canvas без внешних тайлов, красный прямоугольник зоны, синие точки тайлов, жёлтая активная точка, зелёный след пройденного
+ - **Окно захвата** ([capture.html](capture.html)) — Cesium Viewer, прогресс и лог
+
+## Структура проекта
+
+```
+SandCastle/
+├── start.bat Запуск (проверяет Node, принимает порт, стартует сервер)
+├── package.json
+├── server.js Express backend + API
+├── index.html Панель управления
+├── ui.js UI-контроллер панели
+├── style.css
+├── capture.html Окно захвата (Cesium)
+├── mapview.html Окно карты (canvas, без внешних тайлов)
+├── settings.json Последние настройки (создаётся автоматически)
+└── output/ Результаты (создаётся автоматически)
+ └── {dataset}/{country}/{city}/{region}/
+ ├── {region}.txt Углы зоны: maxLat minLon / minLat maxLon
+ ├── DB/
+ │ ├── merge.tif Базовый TIFF сцены (для внешней нарезки)
+ │ ├── db_postion.txt Строки: filename lon lat res_x res_y
+ │ └── img/
+ │ └── crop_{col}_{row}_{size}.png Ортофото по сетке (X=col, Y=row, size=px)
+ ├── positive.json Соответствия query -> точный DB crop
+ ├── semi_positive.json Соответствия query -> соседние DB crop
+ └── query/
+ └── height{H}_rot{R}/
+ ├── height{H}_rot{R}.json Траектория камеры
+ └── footage/
+ └── height{H}_rot{R}_{NN}.jpeg Дроновые кадры
+```
+
+## Формат траектории (UAV-GeoLoc)
+
+`query/height100_rot0/height100_rot0.json`:
+
+```json
+{
+ "name": "height100_rot0",
+ "width": 512,
+ "height": 512,
+ "frameRate": 30,
+ "numFrames": 75,
+ "durationSeconds": 2.5,
+ "cameraFrames": [
+ {
+ "position": { "x": 3843512.1, "y": 604821.7, "z": 5095123.4 },
+ "rotation": { "x": -60, "y": 0, "z": 0 },
+ "coordinate": { "latitude": 52.41, "longitude": 9.65, "altitude": 100 },
+ "fovVertical": 30
+ }
+ ]
+}
+```
+
+`numFrames` и `durationSeconds` вычисляются автоматически по фактическому числу сохранённых кадров.
+
+- `position` — ECEF (Earth-Centered Earth-Fixed) в метрах, получено через `Cesium.Cartesian3.fromDegrees`
+- `rotation` — `x` = pitch, `y` = heading (yaw), `z` = roll, все в градусах (pitch задаётся в UI)
+- `coordinate` — геодезические координаты точки съёмки
+- `fovVertical` — вертикальный FOV камеры Cesium в градусах (задаётся в UI)
+
+## API сервера
+
+| Метод | Путь | Назначение |
+|---|---|---|
+| GET | `/api/get-settings` | Текущие настройки (или дефолты) |
+| POST | `/api/save-settings` | Сохранить настройки в `settings.json` |
+| POST | `/api/create-folders` | Создать дерево сцены и `{region}.txt` |
+| POST | `/api/start-capture` | Сохранить настройки и вернуть URL `capture.html` |
+| POST | `/api/save-db-crop` | Сохранить `crop_{col}_{row}_{size}.png` + строку в `db_postion.txt` |
+| POST | `/api/save-merge-tif` | Сохранить `DB/merge.tif` |
+| POST | `/api/save-drone-frame`| Сохранить кадр `height{H}_rot{R}_{NN}.jpeg` |
+| POST | `/api/save-trajectory` | Сохранить `height{H}_rot{R}.json` |
+| POST | `/api/save-annotations` | Сохранить `positive.json` и `semi_positive.json` |
+
+## Координаты DMS
+
+Формат: `52°25'10.28"N 9°39'10.04"E`. Точки 1 и 2 — противоположные углы прямоугольника; их порядок не важен, сервер сам вычисляет `minLat/maxLat/minLon/maxLon`.
+
+## Параметры съёмки (актуально)
+
+- `DB crop sizes` — обязательный список из **11 уникальных** положительных размеров (px)
+- стандартная кнопка в UI подставляет:
+ - `100,150,200,250,300,400,500,512,600,800,1000`
+- для каждого тайла сохраняется 11 PNG-кропов:
+ - `crop_{col}_{row}_{size}.png`
+- `Query`:
+ - `drone_size` — размер JPEG кадра
+ - `query_fov_deg` — вертикальный FOV
+ - `query_pitch_deg` — зафиксирован на `-90` (top-down)
+- высоты query задаются тремя редактируемыми слотами (`height_100_value`, `height_125_value`, `height_150_value`) и включаются соответствующими чекбоксами
+- при завершении захвата сохраняются:
+ - `DB/merge.tif`
+ - `positive.json`
+ - `semi_positive.json`
+
+## Обмен окнами
+
+`capture.html` ↔ `mapview.html` общаются через `BroadcastChannel('sandcastle-map')`. Сообщения:
+- `{type: 'init', bounds, tiles}` — одноразово после генерации сетки
+- `{type: 'position', index, total, lat, lon}` — перед съёмкой каждого тайла
+- `{type: 'done', total}` — по завершении
+- `{type: 'request-init'}` — карта запрашивает реплей при позднем открытии
+
+## Требования
+
+- Node.js 14+
+- Cesium Ion token (Google Photorealistic 3D Tiles, asset `2275207`) — вшит в `capture.html`
+- Интернет для Cesium CDN и Google 3D Tiles (карта зоны работает офлайн)
+
+## Версия
+
+1.0.0 — Мороз Е.С. Павленко Б. В. / ЛИСАД
diff --git a/UAV-GeoLoc_Dataset_Analysis.md b/UAV-GeoLoc_Dataset_Analysis.md
new file mode 100644
index 0000000..89fef7b
--- /dev/null
+++ b/UAV-GeoLoc_Dataset_Analysis.md
@@ -0,0 +1,688 @@
+```
+АНАЛИЗ ДАТАСЕТА: World-UAV (UAV-GeoLoc)
+```
+Дата анализа: 2026-04-06
+Метод: Полный эмпирический анализ данных на диске + GitHub-репозиторий + проектная
+страница авторов
+Все пробелы из предыдущего анализа закрыты реальными данными.
+1. МЕТАДАННЫЕ
+Поле Значение
+Полное
+название
+UAV-GeoLoc: A Large-Vocabulary Dataset and Geometry-Transformed
+Method for UAV Geo-Localization
+Авторы Rouwan Wu, Jiacheng Deng, Mingyu Mou, Xingyi He, Maojun Zhang, Yu
+```
+Liu, Shen Yan (NUDT + Zhejiang University)
+```
+```
+Год, Venue 2025, IEEE Robotics and Automation Letters (RA-L), IF ~5.2, Q1 Robotics
+```
+DOI 10.1109/LRA.2025.3588061
+```
+Код https://github.com/RingoWRW/GeoLoc-UAV (Python, 17 stars)
+```
+Данные https://huggingface.co/datasets/RingoWRW97/UAV-GeoLoc
+```
+Лицензия CC BY-NC 4.0 (модификация и коммерческое использование
+```
+```
+запрещены)
+```
+Общий
+объём на
+диске
+```
+~181 GB (Country: ~84 GB, Terrain: ~95 GB, Rot: ~1.4 GB, Index +
+```
+```
+weights: ~0.3 GB)
+```
+2. ОБЩАЯ СТАТИСТИКА
+2.1. Сводка по подмножествам
+Параметр Country Terrain Rot ИТОГО
+Категорий верхнего уровня 11 стран 27 типов ландшафта 1 сцена —
+Городов / под-категорий 35 27 1 63
+```
+Сцен (регионов) 171 200 1 372
+```
+Параметр Country Terrain Rot ИТОГО
+```
+DB crop-изображений (спутник) 141,045 132,990 648 274,683
+```
+```
+Query-кадров (дрон) 308,352 337,704 6,688 652,744
+```
+Всего изображений 449,397 470,694 7,336 927,427
+Avg positives / frame 1.00 1.00 1.00 1.00
+Avg semi-positives / frame 2.87 2.80 — ~2.83
+Уникальных размеров crop 6 9 1 11
+```
+2.2. Разбиение train / val / test (Index-файлы, только Terrain split)
+```
+Split Сцен Query-изображений DB-изображений Уник. labels
+Train 140 206,108 94,709 9,370
+Val 40 62,368 26,597 2,757
+Test 20 33,472 11,684 1,439
+Итого 200 301,948 132,990 13,566
+```
+Дополнительные splits: *_country (180/60/30 сцен), *_all (319/100/50 сцен
+```
+```
+объединённо).
+```
+2.3. Распределение positives на query
+```
+Из train_query.txt (206,108 строк):
+```
+Positives на query Количество Доля
+1 ~2,500 1.2%
+2 ~4,800 2.3%
+3 ~2,200 1.1%
+4 ~194,558 94.4%
+5 ~100 <0.1%
+6 ~1,950 0.9%
+```
+Подавляющее большинство query имеют ровно 4 positive DB-патча (1 точный + 3 semi-
+```
+```
+positive соседа из-за 50% overlap нарезки).
+```
+3. ИЕРАРХИЯ СЦЕН
+3.1. Country: 11 стран, 35 городов, 171 сцена
+Страна Городов Сцен Примеры городов
+USA 6 29 Boston, Chicago, Los Angeles, New York, San Francisco,
+Washington
+```
+Australia 3 20 Adelaide (7), Melbourne (6), Sydney (7)
+```
+```
+English 4 20 Birmingham (5), Edinburgh (5), London (5), Manchester (5)
+```
+```
+German 4 19 Berlin (5), Falakford (5), Hamburg (4), Munich (5)
+```
+```
+Italy 4 19 Milan (5), Rome (5), Turin (5), Venice (4)
+```
+```
+French 3 13 Lyons (5), Marseilles (4), Paris (4)
+```
+```
+Japan 3 13 Kyoto (4), Osaka (4), Tokyo (5)
+```
+```
+Korea 2 10 Busan (5), Seoul (5)
+```
+```
+Poland 2 10 Krakow (5), Warsaw (5)
+```
+```
+Spain 2 10 Barcelona (5), Madrid (5)
+```
+```
+Brazil 2 8 Rio de Janeiro (3), Sao Paulo (5)
+```
+```
+Замечание: 17 сцен (Edinburgh 5, London 5, Manchester 5, Birmingham/JewelleryQuarter
+```
+```
+1, Chicago/__MACOSX 1) не содержат DB-кропов и positive.json — неполные данные
+```
+или артефакты архивации.
+3.2. Terrain: 27 типов ландшафта, 200 сцен
+Тип ландшафта Сцен Тип ландшафта Сцен
+Hill 18 Finca 11
+Mountain 18 Plateau 11
+Fall 12 Volcano 11
+Plain 12 Island 10
+Basin 11 Gorge 9
+Delta 11 Wetland 8
+Calcification 6 Prairie 5
+Desert 6 Terrace 5
+Flowers 6 Farm 5
+Тип ландшафта Сцен Тип ландшафта Сцен
+Oasis 6 Glacier 5
+StoneForest 6 Snow 3
+Pasture 2 Danxia 1
+Hylare 1 Karst 1
+3.3. Rot: 1 сцена, 88 вариантов
+```
+Сцена: SouthernSuburbs (Аделаида, Австралия)
+```
+4. ГЕОГРАФИЧЕСКИЙ ОХВАТ
+Параметр Значение
+```
+Диапазон долгот от -155° (Гавайи) до +176° (Новая Зеландия)
+```
+```
+Диапазон широт от -50° (Патагония) до +63° (Гамбург)
+```
+```
+Континентов 6 (Европа, Азия, Северная/Южная Америка, Австралия, Африка —
+```
+```
+через Terrain)
+```
+Климатических
+зон
+Множественные: тропики, субтропики, умеренная, субарктика
+Ключевое преимущество: Первый UAV-to-satellite датасет с мультистрановым
+```
+покрытием (6+ стран и все континенты кроме Антарктиды).
+```
+5. МЕТОДОЛОГИЯ СБОРА ДАННЫХ
+```
+height100: 72 варианта — поворот каждые 5 градусов (0, 5, 10, ..., 355)
+```
+height125/150: по 8 вариантов — поворот каждые 45 градусов
+Итого: 88 вариантов x 76 кадров = 6,688 query-кадров
+```
+DB: 648 кропов (27x24 сетка, 200x200 px)
+```
+5.1. Источники изображений
+```
+Дроновые виды (query):
+```
+Параметр Значение
+```
+Платформа Синтетический рендеринг (Google Earth Studio 3D)
+```
+```
+Тип съёмки RGB, top-down (камера вертикально вниз, pitch ~90°)
+```
+```
+Разрешение кадров 512x512 px (подтверждено из JSON и реальных файлов)
+```
+```
+FOV 30° по вертикали (из camera trajectory JSON)
+```
+Частота кадров 30 fps, 2.5 сек на траекторию
+```
+Кадров на траекторию 76 (75 кадров анимации + начальный)
+```
+Формат JPEG
+```
+Спутниковые виды (gallery / DB):
+```
+Параметр Значение
+```
+Платформа Спутник (Google Earth / Google Maps tiles)
+```
+```
+Формат PNG (кропы), GeoTIFF (merge.tif)
+```
+Разрешение
+кропов
+```
+от 100x100 до 1000x1000 px (11 уникальных размеров, см.
+```
+```
+раздел 6)
+```
+```
+GSD Варьируется по сценам (зависит от zoom level при рендеринге)
+```
+5.2. Параметры съёмки дронов
+Параметр Значение
+```
+Высоты полёта 100 м, 125 м, 150 м (3 значения)
+```
+Азимуты камеры
+```
+(heading)
+```
+```
+0°, 45°, 90°, 135°, 180°, 225°, 270°, 315° (8 направлений, шаг
+```
+```
+45°)
+```
+Число вариантов на
+сцену
+```
+24 (3 высоты x 8 азимутов) для Country; 8-24 для Terrain
+```
+```
+FOV камеры 30° (вертикальный, подтверждено)
+```
+5.3. Примеры данных
+Пары query-positive:
+Влияние высоты на query-изображение:
+Влияние поворота камеры:
+6. ПРОСТРАНСТВЕННАЯ НАРЕЗКА СПУТНИКОВЫХ
+ПАТЧЕЙ
+6.1. Обнаруженные параметры нарезки
+Эмпирически установлено путём попиксельного сопоставления кропов с merge.tif:
+```
+Правило: stride = crop_size / 2 (ровно 50% overlap) для ВСЕХ сцен без
+```
+исключений.
+Параметр
+нарезки
+Значение
+Стратегия Sliding window с фиксированным overlap
+```
+Overlap 50% (stride = crop_size / 2)
+```
+```
+Именование crop_X_Y.png — X по ширине (col), Y по высоте (row)
+```
+Позиция в
+merge.tif
+merge[Y*stride : Y*stride+crop_size, X*stride :
+X*stride+crop_size]
+6.2. Распределение размеров кропов
+```
+Crop size (px) Stride (px) Сцен Country Сцен Terrain Итого сцен
+```
+100x100 50 1 0 1
+150x150 75 ~20 11 ~31
+200x200 100 ~120 111 ~231
+250x250 125 ~15 0 ~15
+300x300 150 0 10 10
+400x400 200 1 3 4
+500x500 250 0 1 1
+512x512 256 4 43 47
+600x600 300 0 8 8
+800x800 400 0 8 8
+1000x1000 500 0 4 4
+```
+Наиболее частый размер: 200x200 px (231 сцена, ~64% всех сцен).
+```
+6.3. Размеры merge.tif
+Размеры merge.tif сильно варьируются:
+7. АННОТАЦИИ И МЕТАДАННЫЕ
+7.1. Файлы аннотаций на сцену
+Файл Содержание Формат
+positive.json Точное GT-соответствие: frame_idx -
+> [crop_name]
+JSON, 1 positive на
+кадр
+Минимум: ~1280x2560 px
+```
+Типичный (Country): ~2816x2304 — 3072x2560 px
+```
+```
+Типичный (Terrain, 512 crop): ~7680x5120 — 7680x7680 px
+```
+```
+Максимум: ~15360x11520 px (отдельные сцены Terrain)
+```
+```
+USA/Washington: ~10752x9984 px (512x512 кропы)
+```
+Файл Содержание Формат
+semi_positive.json Частичные перекрытия: frame_idx ->
+[crop_1, crop_2, ...]
+JSON, 1-5 semi-
+positives
+db_postion.txt GPS-координаты каждого crop filename lon lat
+res_x res_y
+```
+height{H}_rot{R}.json Траектория камеры для каждого
+```
+query-варианта
+JSON с ECEF, lat/lon,
+FOV
+merge.tif Полный спутниковый снимок
+региона
+GeoTIFF
+```
+{RegionName}.txt Границы региона (start/end
+```
+```
+координаты)
+```
+Text
+7.2. Структура camera trajectory JSON
+7.3. Типы аннотаций
+Тип аннотации Наличие Комментарий
+GPS-координаты Да Из Google Earth, точность ~1-3 м
+```
+Высота дрона (altitude) Да 100, 125, 150 м (дискретно)
+```
+```
+Азимут камеры (heading) Да 8 значений с шагом 45°
+```
+FOV камеры Да 30° вертикальный
+```
+Pitch / Roll Да Фиксированный (top-down, ~90°)
+```
+Positive/Semi-positive pairs Да positive.json + semi_positive.json
+Семантические метки Частично 27 категорий terrain type
+```
+{
+```
+"name": "height100_rot0",
+"width": 512, "height": 512,
+"frameRate": 30,
+"numFrames": 75,
+"durationSeconds": 2.5,
+"cameraFrames": [
+```
+{
+```
+```
+"position": {"x": ..., "y": ..., "z": ...},
+```
+```
+"rotation": {"x": ..., "y": ..., "z": ...},
+```
+```
+"coordinate": {"latitude": ..., "longitude": ..., "altitude": 150},
+```
+"fovVertical": 30
+```
+}, ...
+```
+]
+```
+}
+```
+Тип аннотации Наличие Комментарий
+Временная метка Нет —
+Depth maps Нет —
+Segmentation masks Нет —
+Bounding boxes Нет —
+8. ПРЕДОБУЧЕННЫЕ МОДЕЛИ
+```
+Три checkpoint'а (~95 MB каждый), идентичная архитектура:
+```
+Файл Обучение Параметров Описание
+weights_terrain.pth Terrain split 24,873,938 Только природные сцены
+weights_city.pth Country split 24,873,938 Только городские сцены
+```
+weights_e6_all.pth All (combined) 24,873,938 Объединённый датасет
+```
+```
+Архитектура модели (221 слой):
+```
+Архитектуры из GitHub-репозитория:
+Вариант Backbone Local Features Aggregation Rotation Inv.
+```
+Group (основной) DINOv2 ViT-S/14 GroupNet MulConvAP Да (8 углов)
+```
+Group-DINOv2 DINOv2 ViT-B/14 GroupNet ConvAP Да
+Vanilla ResNet50 Global only ConvAP/GeM Нет
+```
+AnyLoc (baseline) DINOv2 ViT-G/14 Global GeM Нет
+```
+9. ПРОТОКОЛ ОЦЕНКИ
+logit_scale — обучаемый скаляр температуры для contrastive loss
+groupnet.extractor — DINOv2 ViT-S/14 с conv projection
+groupnet.embedder — embedding head
+aggregator.AAP — adaptive average pooling
+```
+aggregator.channel_pool_{1,3,5} — multi-scale channel pooling (MulConvAP)
+```
+9.1. Задача
+```
+Image-to-image retrieval (drone -> satellite). Дроновое изображение (query)
+```
+```
+сопоставляется с гео-привязанными спутниковыми патчами (gallery) через ранжирование
+```
+по сходству эмбеддингов.
+Ключевое отличие от University-1652: One-to-N matching — одному query может
+соответствовать несколько positive и semi-positive gallery-патчей.
+9.2. Метрики
+Метрика Используется Комментарий
+Recall@1 Да Стандарт для всех CVGL-работ
+Recall@5 Да —
+Recall@10 Да —
+9.3. Результаты на Rot-подмножестве
+```
+"Fire" — модели, обученные на World-UAV; "Box" — модели с LPN-методом.
+```
+10. ПОЛНАЯ СТРУКТУРА ДАННЫХ
+UAV-GeoLoc/ # ~181 GB
+├── Country/ # ~84 GB, 11 стран, 35 городов, 171
+сцена
+```
+│ └── {Country}/{City}/{Region}/
+```
+│ ├── DB/
+```
+│ │ ├── merge.tif # полный спутниковый снимок (GeoTIFF)
+```
+│ │ ├── db_postion.txt # GPS: filename lon lat res_x res_y
+│ │ └── img/
+```
+│ │ └── crop_X_Y.png # нарезанные патчи (stride =
+```
+```
+crop_size/2)
+```
+│ ├── query/
+```
+│ │ └── height{H}_rot{R}/ # 24 варианта (H: 100/125/150, R: 0-315)
+```
+│ │ ├── footage/
+```
+│ │ │ └── height{H}_rot{R}_{frame}.jpeg # 76 кадров, 512x512
+```
+```
+│ │ ├── height{H}_rot{R}.json # траектория камеры
+```
+```
+│ │ └── height{H}_rot{R}.esp # Earth Studio project
+```
+│ ├── positive.json # GT: frame_idx -> [exact crop match]
+│ ├── semi_positive.json # frame_idx -> [overlapping crops]
+```
+│ └── {Region}.txt # координаты региона
+```
+│
+├── Terrain/ # ~95 GB, 27 типов, 200 сцен
+```
+│ └── {TerrainType}/{Scene}/ # структура аналогична Country
+```
+│ ├── DB/
+│ ├── query/ # 8-24 варианта
+│ ├── positive.json
+│ ├── semi_positive.json
+```
+│ └── visible_gt/ # (опционально) GT видимости
+```
+│
+├── Rot/ # ~1.4 GB, 1 сцена
+```
+│ └── SouthernSuburbs/ # 88 вариантов (72 rot@h100 + 8@h125 +
+```
+```
+8@h150)
+```
+│ ├── DB/ # 648 кропов
+│ └── query/
+│
+├── Index/ # train/val/test splits
+```
+│ ├── train.txt / val.txt / test.txt # списки сцен (Terrain)
+```
+```
+│ ├── train_country.txt / val_country.txt / ...# списки сцен (Country)
+```
+│ ├── train_all.txt / val_all.txt / ... # объединённые
+│ ├── train_query.txt # 206K строк: path label pos1
+[pos2...]
+│ ├── train_db.txt # 95K DB-путей
+│ ├── val_query.txt / val_db.txt # 62K / 27K
+│ └── test_query.txt / test_db.txt # 33K / 12K
+│
+├── weights_terrain.pth # 95 MB, 24.9M params
+├── weights_city.pth # 95 MB, 24.9M params
+11. СРАВНЕНИЕ С КЛЮЧЕВЫМИ ДАТАСЕТАМИ
+Характеристика World-UAV University-1652 SUES-200 VIGOR CVUSA
+Год 2025 2020 2023 2021 2019
+Venue RA-L ACM MM — CVPR —
+Вид пары D<->S D<->S<->G D<->S G<->S G<->S
+Число локаций 372 1,652 200 90K+ 44K
+Число
+изображений
+927K 72K+ 4,800 238K 88K
+```
+Число стран 6+ 1 (Китай) 1 (Китай) 1
+```
+```
+(США)
+```
+```
+1 (США)
+```
+Число типов
+сцен
+```
+27 ~1 (кампус) ~1
+```
+```
+(кампус)
+```
+смеш. смеш.
+Разрешение
+query
+512x512 256x256 224x224 varies varies
+Видов на
+локацию
+```
+24 (D) 54 (D) 4 (D) 1 1
+```
+Мульти-
+высотность
+Да
+```
+(100/125/150 м)
+```
+```
+Да (3 выс.) Да (4
+```
+```
+выс.)
+```
+Нет Нет
+Мульти-
+ракурсность
+```
+Да (8 азимутов) Да (18 аз.) Нет Нет Нет
+```
+One-to-N
+matching
+Да Нет Нет Да Нет
+Semi-positives Да Нет Нет Нет Нет
+```
+Cross-area split Да (по
+```
+```
+странам)
+```
+Нет Нет Да Нет
+Gallery strategy Grid + 50%
+overlap
+GPS-centered
+1:1
+GPS-
+centered
+1:1
+Grid,
+semi-
+pos.
+GPS-
+centered
+1:1
+```
+Gallery size (DB) 274K 951 200 90K+ 44K
+```
+Дрон-виды Синтетические
+```
+(GE)
+```
+Синтетические
+```
+(GE)
+```
+Реальные — —
+├── weights_e6_all.pth # 95 MB, 24.9M params
+└── README.md
+Характеристика World-UAV University-1652 SUES-200 VIGOR CVUSA
+```
+Публичный Да (CC BY-NC
+```
+```
+4.0)
+```
+Да Да Да Да
+12. ЗАМЕЧАНИЯ ПО КАЧЕСТВУ ДАННЫХ
+```
+12.1. Неполные сцены (17 из 171 Country)
+```
+Следующие сцены не содержат DB-кропов, GPS-данных и positive.json:
+```
+12.2. Ошибки именования папок (6+ сцен)
+```
+Сцена Ошибка Ожидание
+French/Lyons/PartDieu eight150_rot135 height150_rot135
+French/Marseilles/LaPlaine 125_rot315 height125_rot315
+Spain/Barcelona/SantMarti ght100_rot90 height100_rot90
+Japan/Tokyo/Shibuya height150_rot3155 height150_rot315
+Italy/Turin/Crocetta height150_rot22 height150_rot225
+```
+English/Birmingham/Harborne height150_rot0 (1) дубликат
+```
+12.3. Общие замечания
+```
+Edinburgh: все 5 (CastleHill, Dalry, Haymarket, NewTown, Stockbride)
+```
+```
+London: все 5 (CamdenTown, CoventGarden, Fitzrovia, Mayfair, SoHo)
+```
+```
+Manchester: все 5 (Ancoats, Castlefield, Deansgate, NorthernQuarter, Piccadilly)
+```
+```
+Birmingham: JewelleryQuarter
+```
+```
+Chicago: __MACOSX (артефакт macOS-архивации)
+```
+```
+Domain gap: дроновые виды синтетические (Google Earth 3D) — не реальные БПЛА
+```
+Нет сезонной вариативности: Google Earth рендерит одну "замороженную" версию
+сцены
+Нет погодных / световых вариаций: нет fog, rain, night
+Воспроизводимость: Google периодически обновляет 3D-модели, что может
+нарушить воспроизводимость
+```
+Географический bias: нет Африки (кроме Terrain), нет Юго-Восточной Азии, нет
+```
+арктических регионов в Country
+13. КЛЮЧЕВЫЕ ЧИСЛА ДЛЯ ЦИТИРОВАНИЯ
+Метрика Значение
+Всего сцен 372
+Всего изображений 927,427
+```
+Стран 11 (Country)
+```
+```
+Типов ландшафта 27 (Terrain)
+```
+Query-кадров 652,744
+DB-кропов 274,683
+Размер на диске ~181 GB
+Разрешение query 512x512 px
+FOV камеры 30°
+```
+Высоты 3 (100, 125, 150 м)
+```
+```
+Азимуты 8 (шаг 45°)
+```
+```
+Overlap нарезки 50% (stride = crop_size / 2)
+```
+```
+Crop sizes 11 уникальных (от 100 до 1000 px)
+```
+```
+Модель (params) 24.9M (GroupNet + DINOv2-ViT-S/14 + MulConvAP)
+```
\ No newline at end of file
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..eae292d
--- /dev/null
+++ b/app.js
@@ -0,0 +1,217 @@
+import * as Cesium from "cesium";
+
+
+Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3NjcxNWZiYy1mZjFiLTQzNGEtYmEwNy1hMmU0ZDg4NWI2ZGUiLCJpZCI6Mzc3NjU2LCJpYXQiOjE3NjgyMTk2NzJ9.D5DL5zrDi4yK6OPgq-ypC4hM5hfqYKODvppgakc7y3g";
+
+const creditContainer = document.createElement('div');
+creditContainer.style.display = 'none';
+
+const viewer = new Cesium.Viewer("cesiumContainer", {
+ timeline: false,
+ animation: false,
+ sceneModePicker: false,
+ baseLayerPicker: false,
+ geocoder: false,
+ homeButton: false,
+ navigationHelpButton: false,
+ terrainProvider: await Cesium.createWorldTerrainAsync(),
+ creditContainer: creditContainer,
+ contextOptions: {
+ webgl: {
+ preserveDrawingBuffer: true
+ }
+ }
+});
+
+try {
+ const tileset = await Cesium.Cesium3DTileset.fromIonAssetId(2275207);
+ viewer.scene.primitives.add(tileset);
+} catch (error) {
+ console.log(error);
+}
+
+/*
+=================== Настройка отображения ===================
+*/
+
+const outputWidth = 512;
+const outputHeight = 512;
+
+viewer.scene.canvas.style.width = outputWidth + 'px';
+viewer.scene.canvas.style.height = outputHeight + 'px';
+viewer.container.style.width = outputWidth + 'px';
+viewer.container.style.height = outputHeight + 'px';
+
+viewer.resize(); // важно: обновляет внутренние размеры рендерера
+
+/*
+=================== Вспомагательные функции ===================
+*/
+
+function dmsToDecimal(dms) {
+ // Пример входа: "48°18'20\"N" или "16°24'41\"E"
+ const regex = /(\d+)°(\d+)'(\d+)"?([NSEW])/i;
+ const match = dms.match(regex);
+ if (!match) {
+ throw new Error("Неверный формат координат: " + dms);
+ }
+
+ const degrees = parseInt(match[1], 10);
+ const minutes = parseInt(match[2], 10);
+ const seconds = parseInt(match[3], 10);
+ const direction = match[4].toUpperCase();
+
+ let decimal = degrees + minutes / 60 + seconds / 3600;
+
+ // Южная и Западная широты — отрицательные
+ if (direction === "S" || direction === "W") {
+ decimal = -decimal;
+ }
+
+ return decimal;
+}
+
+function metersToLat(m) {
+ return m / METERS_PER_DEG_LAT;
+}
+
+function metersToLon(m, latDeg) {
+ return m / (METERS_PER_DEG_LAT * Math.cos(Cesium.Math.toRadians(latDeg)));
+}
+
+function saveImage(filename) {
+ const canvas = viewer.scene.canvas;
+ const image = canvas.toDataURL("image/png");
+
+ const link = document.createElement("a");
+ link.href = image;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+}
+
+function downloadFile(filename, content, mime = "text/plain") {
+ const blob = new Blob([content], { type: mime });
+ const url = URL.createObjectURL(blob);
+
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+}
+
+/*
+=================== Константы и переменные ===================
+*/
+
+const METERS_PER_DEG_LAT = 111_320;
+
+// ---- Параметры сетки ----
+
+const GRID = 2;
+const TILE_M = 50;
+const CAMERA_HEIGHT = 450;
+
+// ---- Координаты ----
+
+const latDMS = "45°25'3\"N";
+const lonDMS = "9°04'15\"E";
+//45°25'35"N 9°04'15"E
+
+const half = (GRID - 1) * TILE_M / 2;
+
+const CENTER_LAT = dmsToDecimal(latDMS);
+const CENTER_LON = dmsToDecimal(lonDMS);
+const startLat = CENTER_LAT + metersToLat(half);
+const startLon = CENTER_LON - metersToLon(half, CENTER_LAT);
+
+
+let row = 0;
+let col = 0;
+let index = 0;
+const offset = 0.0005;
+
+/*
+=================== Главный цикл сбора ===================
+*/
+
+var flag = false;
+
+function captureNextTile() {
+
+ if (flag == false) {
+ setTimeout(() => {
+ flag = true;
+ }, 5000)
+ }
+
+ const lat = startLat - metersToLat(row * TILE_M);
+ const lon = startLon + metersToLon(col * TILE_M, lat);
+
+ downloadFile(
+ `tile_${index}_sat.json`,
+ JSON.stringify({ lat: lat, lon: lon }, null, 2),
+ "application/json"
+ );
+
+ // ---------- Спутник ----------
+ viewer.camera.setView({
+ destination: Cesium.Cartesian3.fromDegrees(lon, lat, CAMERA_HEIGHT),
+ orientation: {
+ heading: Cesium.Math.toRadians(0),
+ pitch: Cesium.Math.toRadians(-90),
+ roll: 0
+ }
+ });
+
+ viewer.scene.requestRender();
+
+ setTimeout(() => {
+ saveImage(`tile_${index}_sat.png`);
+
+ // ---------- Дрон ----------
+ viewer.camera.setView({
+ destination: Cesium.Cartesian3.fromDegrees(lon, lat - offset, CAMERA_HEIGHT),
+ orientation: {
+ heading: Cesium.Math.toRadians(0),
+ pitch: Cesium.Math.toRadians(-60),
+ roll: 0
+ }
+ });
+
+ viewer.scene.requestRender();
+
+ setTimeout(() => {
+ saveImage(`tile_${index}_drone.png`);
+
+ // ---- След. ячейка ----
+ col++;
+ if (col >= GRID) {
+ col = 0;
+ row++;
+ }
+ index++;
+
+ captureNextTile();
+ }, 2500); // Задержка дрон-рендера
+
+ }, 2500); // Задержка спутник-рендера
+}
+
+/*
+=================== Запуск ===================
+*/
+
+viewer.scene.postRender.addEventListener(function once() {
+ viewer.scene.postRender.removeEventListener(once);
+
+ setTimeout(() => {
+ captureNextTile();
+ }, 9000);
+});
+
diff --git a/capture.html b/capture.html
new file mode 100644
index 0000000..95104a9
--- /dev/null
+++ b/capture.html
@@ -0,0 +1,908 @@
+
+
+
+
+
+
+ Захват изображений
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/graphify-out/GRAPH_REPORT.md b/graphify-out/GRAPH_REPORT.md
new file mode 100644
index 0000000..017baa0
--- /dev/null
+++ b/graphify-out/GRAPH_REPORT.md
@@ -0,0 +1,86 @@
+# Graph Report - d:/Int SandCastle (2026-04-15)
+
+## Corpus Check
+- Large corpus: 1277 files · ~9,282,356 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+
+## Summary
+- 77 nodes · 113 edges · 6 communities detected
+- Extraction: 94% EXTRACTED · 6% INFERRED · 0% AMBIGUOUS · INFERRED: 7 edges (avg confidence: 0.84)
+- Token cost: 0 input · 0 output
+
+## Community Hubs (Navigation)
+- [[_COMMUNITY_REST API + Control Panel|REST API + Control Panel]]
+- [[_COMMUNITY_UI Controller (ui.js)|UI Controller (ui.js)]]
+- [[_COMMUNITY_Cesium Capture Pipeline|Cesium Capture Pipeline]]
+- [[_COMMUNITY_Coordinate Math Helpers|Coordinate Math Helpers]]
+- [[_COMMUNITY_Legacy app.js|Legacy app.js]]
+- [[_COMMUNITY_UAV-GeoLoc Dataset Tree|UAV-GeoLoc Dataset Tree]]
+
+## God Nodes (most connected - your core abstractions)
+1. `SandCastleUI` - 15 edges
+2. `Control Panel (index.html)` - 9 edges
+3. `server.js (Express backend)` - 9 edges
+4. `generateCoordinates()` - 6 edges
+5. `output/ directory tree` - 5 edges
+6. `cameraFrames` - 5 edges
+7. `captureNextTile()` - 4 edges
+8. `getBoundsFromSettings()` - 4 edges
+9. `Cesium` - 4 edges
+10. `Camera trajectory JSON` - 4 edges
+
+## Surprising Connections (you probably didn't know these)
+- `server.js (Express backend)` --exposes--> `POST /api/save-db-crop` [EXTRACTED]
+ README.md → README.md _Bridges community 0 → community 5_
+- `server.js (Express backend)` --exposes--> `POST /api/save-trajectory` [EXTRACTED]
+ README.md → README.md _Bridges community 0 → community 2_
+
+## Hyperedges (group relationships)
+- **Capture pipeline** — readme_control_panel, readme_capture_html, readme_mapview_html, readme_broadcastchannel, readme_server_js [EXTRACTED 1.00]
+- **Trajectory schema fields** — readme_camera_frames, readme_ecef_position, readme_rotation, readme_coordinate, readme_fov_vertical [EXTRACTED 1.00]
+- **UAV-GeoLoc output layout** — readme_output_tree, readme_db_crop, readme_db_position_txt, readme_drone_frame, readme_trajectory, readme_region_txt [EXTRACTED 1.00]
+
+## Communities
+
+### Community 0 - "REST API + Control Panel"
+Cohesion: 0.13
+Nodes (18): POST /api/create-folders, GET /api/get-settings, POST /api/save-settings, POST /api/start-capture, Control Panel (index.html), DMS coordinates, Express, Grid step (meters) (+10 more)
+
+### Community 1 - "UI Controller (ui.js)"
+Cohesion: 0.28
+Nodes (1): SandCastleUI
+
+### Community 2 - "Cesium Capture Pipeline"
+Cohesion: 0.15
+Nodes (16): POST /api/save-trajectory, BroadcastChannel('sandcastle-map'), cameraFrames, capture.html (Cesium Viewer), Cesium, Cesium Ion Token (asset 2275207), coordinate (geodetic), ECEF position (Cartesian3.fromDegrees) (+8 more)
+
+### Community 3 - "Coordinate Math Helpers"
+Cohesion: 0.26
+Nodes (10): computeGrid(), decimalToDMS(), generateCoordinates(), getBoundsFromSettings(), getScenePath(), metersToLat(), metersToLon(), normalizeSettings() (+2 more)
+
+### Community 4 - "Legacy app.js"
+Cohesion: 0.43
+Nodes (4): captureNextTile(), downloadFile(), metersToLat(), metersToLon()
+
+### Community 5 - "UAV-GeoLoc Dataset Tree"
+Cohesion: 0.33
+Nodes (7): POST /api/save-db-crop, POST /api/save-drone-frame, Dataset/Country/City/Region tree, DB crop (ortho crop), db_postion.txt, Drone frame (query), output/ directory tree
+
+## Knowledge Gaps
+- **16 isolated node(s):** `Cesium Ion Token (asset 2275207)`, `ui.js (UI controller)`, `package.json`, `db_postion.txt`, `{region}.txt (bounds)` (+11 more)
+ These have ≤1 connection - possible missing edges or undocumented components.
+
+## Suggested Questions
+_Questions this graph is uniquely positioned to answer:_
+
+- **Why does `Camera trajectory JSON` connect `Cesium Capture Pipeline` to `UAV-GeoLoc Dataset Tree`?**
+ _High betweenness centrality (0.131) - this node is a cross-community bridge._
+- **Why does `server.js (Express backend)` connect `REST API + Control Panel` to `Cesium Capture Pipeline`, `UAV-GeoLoc Dataset Tree`?**
+ _High betweenness centrality (0.110) - this node is a cross-community bridge._
+- **Why does `output/ directory tree` connect `UAV-GeoLoc Dataset Tree` to `REST API + Control Panel`, `Cesium Capture Pipeline`?**
+ _High betweenness centrality (0.086) - this node is a cross-community bridge._
+- **Are the 4 inferred relationships involving `Control Panel (index.html)` (e.g. with `ui.js (UI controller)` and `POST /api/save-settings`) actually correct?**
+ _`Control Panel (index.html)` has 4 INFERRED edges - model-reasoned connections that need verification._
+- **What connects `Cesium Ion Token (asset 2275207)`, `ui.js (UI controller)`, `package.json` to the rest of the system?**
+ _16 weakly-connected nodes found - possible documentation gaps or missing edges._
+- **Should `REST API + Control Panel` be split into smaller, more focused modules?**
+ _Cohesion score 0.13 - nodes in this community are weakly interconnected._
\ No newline at end of file
diff --git a/graphify-out/cache/22c703cdee528e9e63d53a961b656d6da97a788db146a4f6f81e57e9cc29ab68.json b/graphify-out/cache/22c703cdee528e9e63d53a961b656d6da97a788db146a4f6f81e57e9cc29ab68.json
new file mode 100644
index 0000000..00f6387
--- /dev/null
+++ b/graphify-out/cache/22c703cdee528e9e63d53a961b656d6da97a788db146a4f6f81e57e9cc29ab68.json
@@ -0,0 +1 @@
+{"nodes": [{"id": "server_js", "label": "server.js", "file_type": "code", "source_file": "server.js", "source_location": "L1"}, {"id": "server_meterstolat", "label": "metersToLat()", "file_type": "code", "source_file": "server.js", "source_location": "L17"}, {"id": "server_meterstolon", "label": "metersToLon()", "file_type": "code", "source_file": "server.js", "source_location": "L21"}, {"id": "server_dmstodecimal", "label": "dmsToDecimal()", "file_type": "code", "source_file": "server.js", "source_location": "L25"}, {"id": "server_parsecoordinatepair", "label": "parseCoordinatePair()", "file_type": "code", "source_file": "server.js", "source_location": "L44"}, {"id": "server_getboundsfromsettings", "label": "getBoundsFromSettings()", "file_type": "code", "source_file": "server.js", "source_location": "L65"}, {"id": "server_decimaltodms", "label": "decimalToDMS()", "file_type": "code", "source_file": "server.js", "source_location": "L84"}, {"id": "server_normalizesettings", "label": "normalizeSettings()", "file_type": "code", "source_file": "server.js", "source_location": "L103"}, {"id": "server_computegrid", "label": "computeGrid()", "file_type": "code", "source_file": "server.js", "source_location": "L135"}, {"id": "server_generatecoordinates", "label": "generateCoordinates()", "file_type": "code", "source_file": "server.js", "source_location": "L144"}, {"id": "server_dataurltobuffer", "label": "dataUrlToBuffer()", "file_type": "code", "source_file": "server.js", "source_location": "L173"}, {"id": "server_sanitizenamesegment", "label": "sanitizeNameSegment()", "file_type": "code", "source_file": "server.js", "source_location": "L186"}, {"id": "server_getscenepath", "label": "getScenePath()", "file_type": "code", "source_file": "server.js", "source_location": "L194"}], "edges": [{"source": "server_js", "target": "server_meterstolat", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L17", "weight": 1.0}, {"source": "server_js", "target": "server_meterstolon", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L21", "weight": 1.0}, {"source": "server_js", "target": "server_dmstodecimal", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L25", "weight": 1.0}, {"source": "server_js", "target": "server_parsecoordinatepair", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L44", "weight": 1.0}, {"source": "server_js", "target": "server_getboundsfromsettings", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L65", "weight": 1.0}, {"source": "server_js", "target": "server_decimaltodms", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L84", "weight": 1.0}, {"source": "server_js", "target": "server_normalizesettings", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L103", "weight": 1.0}, {"source": "server_js", "target": "server_computegrid", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L135", "weight": 1.0}, {"source": "server_js", "target": "server_generatecoordinates", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L144", "weight": 1.0}, {"source": "server_js", "target": "server_dataurltobuffer", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L173", "weight": 1.0}, {"source": "server_js", "target": "server_sanitizenamesegment", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L186", "weight": 1.0}, {"source": "server_js", "target": "server_getscenepath", "relation": "contains", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L194", "weight": 1.0}, {"source": "server_getboundsfromsettings", "target": "server_parsecoordinatepair", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L73", "weight": 1.0}, {"source": "server_normalizesettings", "target": "server_getboundsfromsettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L131", "weight": 1.0}, {"source": "server_generatecoordinates", "target": "server_getboundsfromsettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L145", "weight": 1.0}, {"source": "server_generatecoordinates", "target": "server_computegrid", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L148", "weight": 1.0}, {"source": "server_generatecoordinates", "target": "server_meterstolat", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L155", "weight": 1.0}, {"source": "server_generatecoordinates", "target": "server_meterstolon", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L156", "weight": 1.0}, {"source": "server_generatecoordinates", "target": "server_decimaltodms", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L164", "weight": 1.0}, {"source": "server_getscenepath", "target": "server_sanitizenamesegment", "relation": "calls", "confidence": "EXTRACTED", "source_file": "server.js", "source_location": "L197", "weight": 1.0}], "raw_calls": [{"caller_nid": "server_meterstolon", "callee": "cos", "source_file": "server.js", "source_location": "L22"}, {"caller_nid": "server_dmstodecimal", "callee": "match", "source_file": "server.js", "source_location": "L26"}, {"caller_nid": "server_dmstodecimal", "callee": "trim", "source_file": "server.js", "source_location": "L26"}, {"caller_nid": "server_dmstodecimal", "callee": "String", "source_file": "server.js", "source_location": "L26"}, {"caller_nid": "server_dmstodecimal", "callee": "parseInt", "source_file": "server.js", "source_location": "L31"}, {"caller_nid": "server_dmstodecimal", "callee": "parseInt", "source_file": "server.js", "source_location": "L32"}, {"caller_nid": "server_dmstodecimal", "callee": "parseFloat", "source_file": "server.js", "source_location": "L33"}, {"caller_nid": "server_dmstodecimal", "callee": "toUpperCase", "source_file": "server.js", "source_location": "L34"}, {"caller_nid": "server_parsecoordinatepair", "callee": "match", "source_file": "server.js", "source_location": "L45"}, {"caller_nid": "server_parsecoordinatepair", "callee": "String", "source_file": "server.js", "source_location": "L45"}, {"caller_nid": "server_parsecoordinatepair", "callee": "map", "source_file": "server.js", "source_location": "L50"}, {"caller_nid": "server_parsecoordinatepair", "callee": "find", "source_file": "server.js", "source_location": "L55"}, {"caller_nid": "server_parsecoordinatepair", "callee": "find", "source_file": "server.js", "source_location": "L56"}, {"caller_nid": "server_getboundsfromsettings", "callee": "trim", "source_file": "server.js", "source_location": "L66"}, {"caller_nid": "server_getboundsfromsettings", "callee": "String", "source_file": "server.js", "source_location": "L66"}, {"caller_nid": "server_getboundsfromsettings", "callee": "trim", "source_file": "server.js", "source_location": "L67"}, {"caller_nid": "server_getboundsfromsettings", "callee": "String", "source_file": "server.js", "source_location": "L67"}, {"caller_nid": "server_getboundsfromsettings", "callee": "min", "source_file": "server.js", "source_location": "L77"}, {"caller_nid": "server_getboundsfromsettings", "callee": "max", "source_file": "server.js", "source_location": "L78"}, {"caller_nid": "server_getboundsfromsettings", "callee": "min", "source_file": "server.js", "source_location": "L79"}, {"caller_nid": "server_getboundsfromsettings", "callee": "max", "source_file": "server.js", "source_location": "L80"}, {"caller_nid": "server_decimaltodms", "callee": "abs", "source_file": "server.js", "source_location": "L85"}, {"caller_nid": "server_decimaltodms", "callee": "floor", "source_file": "server.js", "source_location": "L86"}, {"caller_nid": "server_decimaltodms", "callee": "floor", "source_file": "server.js", "source_location": "L88"}, {"caller_nid": "server_decimaltodms", "callee": "round", "source_file": "server.js", "source_location": "L89"}, {"caller_nid": "server_normalizesettings", "callee": "trim", "source_file": "server.js", "source_location": "L106"}, {"caller_nid": "server_normalizesettings", "callee": "String", "source_file": "server.js", "source_location": "L106"}, {"caller_nid": "server_normalizesettings", "callee": "trim", "source_file": "server.js", "source_location": "L107"}, {"caller_nid": "server_normalizesettings", "callee": "String", "source_file": "server.js", "source_location": "L107"}, {"caller_nid": "server_normalizesettings", "callee": "trim", "source_file": "server.js", "source_location": "L108"}, {"caller_nid": "server_normalizesettings", "callee": "String", "source_file": "server.js", "source_location": "L108"}, {"caller_nid": "server_normalizesettings", "callee": "trim", "source_file": "server.js", "source_location": "L109"}, {"caller_nid": "server_normalizesettings", "callee": "String", "source_file": "server.js", "source_location": "L109"}, {"caller_nid": "server_normalizesettings", "callee": "trim", "source_file": "server.js", "source_location": "L110"}, {"caller_nid": "server_normalizesettings", "callee": "String", "source_file": "server.js", "source_location": "L110"}, {"caller_nid": "server_normalizesettings", "callee": "trim", "source_file": "server.js", "source_location": "L111"}, {"caller_nid": "server_normalizesettings", "callee": "String", "source_file": "server.js", "source_location": "L111"}, {"caller_nid": "server_normalizesettings", "callee": "Number", "source_file": "server.js", "source_location": "L112"}, {"caller_nid": "server_normalizesettings", "callee": "max", "source_file": "server.js", "source_location": "L113"}, {"caller_nid": "server_normalizesettings", "callee": "parseInt", "source_file": "server.js", "source_location": "L113"}, {"caller_nid": "server_normalizesettings", "callee": "Boolean", "source_file": "server.js", "source_location": "L114"}, {"caller_nid": "server_normalizesettings", "callee": "Boolean", "source_file": "server.js", "source_location": "L115"}, {"caller_nid": "server_normalizesettings", "callee": "Boolean", "source_file": "server.js", "source_location": "L116"}, {"caller_nid": "server_normalizesettings", "callee": "Boolean", "source_file": "server.js", "source_location": "L117"}, {"caller_nid": "server_normalizesettings", "callee": "max", "source_file": "server.js", "source_location": "L118"}, {"caller_nid": "server_normalizesettings", "callee": "parseInt", "source_file": "server.js", "source_location": "L118"}, {"caller_nid": "server_normalizesettings", "callee": "max", "source_file": "server.js", "source_location": "L119"}, {"caller_nid": "server_normalizesettings", "callee": "parseInt", "source_file": "server.js", "source_location": "L119"}, {"caller_nid": "server_normalizesettings", "callee": "Boolean", "source_file": "server.js", "source_location": "L120"}, {"caller_nid": "server_normalizesettings", "callee": "Boolean", "source_file": "server.js", "source_location": "L121"}, {"caller_nid": "server_normalizesettings", "callee": "isFinite", "source_file": "server.js", "source_location": "L124"}, {"caller_nid": "server_computegrid", "callee": "cos", "source_file": "server.js", "source_location": "L138"}, {"caller_nid": "server_computegrid", "callee": "max", "source_file": "server.js", "source_location": "L139"}, {"caller_nid": "server_computegrid", "callee": "floor", "source_file": "server.js", "source_location": "L139"}, {"caller_nid": "server_computegrid", "callee": "max", "source_file": "server.js", "source_location": "L140"}, {"caller_nid": "server_computegrid", "callee": "floor", "source_file": "server.js", "source_location": "L140"}, {"caller_nid": "server_generatecoordinates", "callee": "max", "source_file": "server.js", "source_location": "L146"}, {"caller_nid": "server_generatecoordinates", "callee": "Number", "source_file": "server.js", "source_location": "L146"}, {"caller_nid": "server_generatecoordinates", "callee": "max", "source_file": "server.js", "source_location": "L147"}, {"caller_nid": "server_generatecoordinates", "callee": "push", "source_file": "server.js", "source_location": "L158"}, {"caller_nid": "server_dataurltobuffer", "callee": "match", "source_file": "server.js", "source_location": "L178"}, {"caller_nid": "server_dataurltobuffer", "callee": "from", "source_file": "server.js", "source_location": "L183"}, {"caller_nid": "server_sanitizenamesegment", "callee": "replace", "source_file": "server.js", "source_location": "L187"}, {"caller_nid": "server_sanitizenamesegment", "callee": "replace", "source_file": "server.js", "source_location": "L187"}, {"caller_nid": "server_sanitizenamesegment", "callee": "trim", "source_file": "server.js", "source_location": "L187"}, {"caller_nid": "server_sanitizenamesegment", "callee": "String", "source_file": "server.js", "source_location": "L187"}, {"caller_nid": "server_getscenepath", "callee": "join", "source_file": "server.js", "source_location": "L195"}]}
\ No newline at end of file
diff --git a/graphify-out/cache/65ff875203817b898ff241da68adf25c352f2c6709ad3bf1285f09bc3a5648f8.json b/graphify-out/cache/65ff875203817b898ff241da68adf25c352f2c6709ad3bf1285f09bc3a5648f8.json
new file mode 100644
index 0000000..a96bb21
--- /dev/null
+++ b/graphify-out/cache/65ff875203817b898ff241da68adf25c352f2c6709ad3bf1285f09bc3a5648f8.json
@@ -0,0 +1 @@
+{"nodes": [{"id": "app_js", "label": "app.js", "file_type": "code", "source_file": "app.js", "source_location": "L1"}, {"id": "app_dmstodecimal", "label": "dmsToDecimal()", "file_type": "code", "source_file": "app.js", "source_location": "L51"}, {"id": "app_meterstolat", "label": "metersToLat()", "file_type": "code", "source_file": "app.js", "source_location": "L74"}, {"id": "app_meterstolon", "label": "metersToLon()", "file_type": "code", "source_file": "app.js", "source_location": "L78"}, {"id": "app_saveimage", "label": "saveImage()", "file_type": "code", "source_file": "app.js", "source_location": "L82"}, {"id": "app_downloadfile", "label": "downloadFile()", "file_type": "code", "source_file": "app.js", "source_location": "L94"}, {"id": "app_capturenexttile", "label": "captureNextTile()", "file_type": "code", "source_file": "app.js", "source_location": "L145"}], "edges": [{"source": "app_js", "target": "cesium", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L1", "weight": 1.0}, {"source": "app_js", "target": "app_dmstodecimal", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L51", "weight": 1.0}, {"source": "app_js", "target": "app_meterstolat", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L74", "weight": 1.0}, {"source": "app_js", "target": "app_meterstolon", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L78", "weight": 1.0}, {"source": "app_js", "target": "app_saveimage", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L82", "weight": 1.0}, {"source": "app_js", "target": "app_downloadfile", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L94", "weight": 1.0}, {"source": "app_js", "target": "app_capturenexttile", "relation": "contains", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L145", "weight": 1.0}, {"source": "app_capturenexttile", "target": "app_meterstolat", "relation": "calls", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L153", "weight": 1.0}, {"source": "app_capturenexttile", "target": "app_meterstolon", "relation": "calls", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L154", "weight": 1.0}, {"source": "app_capturenexttile", "target": "app_downloadfile", "relation": "calls", "confidence": "EXTRACTED", "source_file": "app.js", "source_location": "L156", "weight": 1.0}], "raw_calls": [{"caller_nid": "app_dmstodecimal", "callee": "match", "source_file": "app.js", "source_location": "L54"}, {"caller_nid": "app_dmstodecimal", "callee": "parseInt", "source_file": "app.js", "source_location": "L59"}, {"caller_nid": "app_dmstodecimal", "callee": "parseInt", "source_file": "app.js", "source_location": "L60"}, {"caller_nid": "app_dmstodecimal", "callee": "parseInt", "source_file": "app.js", "source_location": "L61"}, {"caller_nid": "app_dmstodecimal", "callee": "toUpperCase", "source_file": "app.js", "source_location": "L62"}, {"caller_nid": "app_meterstolon", "callee": "cos", "source_file": "app.js", "source_location": "L79"}, {"caller_nid": "app_meterstolon", "callee": "toRadians", "source_file": "app.js", "source_location": "L79"}, {"caller_nid": "app_saveimage", "callee": "toDataURL", "source_file": "app.js", "source_location": "L84"}, {"caller_nid": "app_saveimage", "callee": "createElement", "source_file": "app.js", "source_location": "L86"}, {"caller_nid": "app_saveimage", "callee": "appendChild", "source_file": "app.js", "source_location": "L89"}, {"caller_nid": "app_saveimage", "callee": "click", "source_file": "app.js", "source_location": "L90"}, {"caller_nid": "app_saveimage", "callee": "removeChild", "source_file": "app.js", "source_location": "L91"}, {"caller_nid": "app_downloadfile", "callee": "createObjectURL", "source_file": "app.js", "source_location": "L96"}, {"caller_nid": "app_downloadfile", "callee": "createElement", "source_file": "app.js", "source_location": "L98"}, {"caller_nid": "app_downloadfile", "callee": "appendChild", "source_file": "app.js", "source_location": "L101"}, {"caller_nid": "app_downloadfile", "callee": "click", "source_file": "app.js", "source_location": "L102"}, {"caller_nid": "app_downloadfile", "callee": "removeChild", "source_file": "app.js", "source_location": "L104"}, {"caller_nid": "app_downloadfile", "callee": "revokeObjectURL", "source_file": "app.js", "source_location": "L105"}, {"caller_nid": "app_capturenexttile", "callee": "setTimeout", "source_file": "app.js", "source_location": "L148"}, {"caller_nid": "app_capturenexttile", "callee": "stringify", "source_file": "app.js", "source_location": "L158"}, {"caller_nid": "app_capturenexttile", "callee": "setView", "source_file": "app.js", "source_location": "L163"}, {"caller_nid": "app_capturenexttile", "callee": "fromDegrees", "source_file": "app.js", "source_location": "L164"}, {"caller_nid": "app_capturenexttile", "callee": "toRadians", "source_file": "app.js", "source_location": "L166"}, {"caller_nid": "app_capturenexttile", "callee": "toRadians", "source_file": "app.js", "source_location": "L167"}, {"caller_nid": "app_capturenexttile", "callee": "requestRender", "source_file": "app.js", "source_location": "L172"}, {"caller_nid": "app_capturenexttile", "callee": "setTimeout", "source_file": "app.js", "source_location": "L174"}]}
\ No newline at end of file
diff --git a/graphify-out/cache/d9926deab971545bafef0edc95312d0cde26e4f5b7486db698de1332c03510de.json b/graphify-out/cache/d9926deab971545bafef0edc95312d0cde26e4f5b7486db698de1332c03510de.json
new file mode 100644
index 0000000..6fe3b50
--- /dev/null
+++ b/graphify-out/cache/d9926deab971545bafef0edc95312d0cde26e4f5b7486db698de1332c03510de.json
@@ -0,0 +1 @@
+{"nodes": [{"id": "ui_js", "label": "ui.js", "file_type": "code", "source_file": "ui.js", "source_location": "L1"}, {"id": "ui_sandcastleui", "label": "SandCastleUI", "file_type": "code", "source_file": "ui.js", "source_location": "L3"}, {"id": "ui_sandcastleui_constructor", "label": ".constructor()", "file_type": "code", "source_file": "ui.js", "source_location": "L4"}, {"id": "ui_sandcastleui_initializeelements", "label": ".initializeElements()", "file_type": "code", "source_file": "ui.js", "source_location": "L35"}, {"id": "ui_sandcastleui_attacheventlisteners", "label": ".attachEventListeners()", "file_type": "code", "source_file": "ui.js", "source_location": "L65"}, {"id": "ui_sandcastleui_log", "label": ".log()", "file_type": "code", "source_file": "ui.js", "source_location": "L84"}, {"id": "ui_sandcastleui_updatestatus", "label": ".updateStatus()", "file_type": "code", "source_file": "ui.js", "source_location": "L93"}, {"id": "ui_sandcastleui_dmstodecimal", "label": ".dmsToDecimal()", "file_type": "code", "source_file": "ui.js", "source_location": "L98"}, {"id": "ui_sandcastleui_parsecoordinatepair", "label": ".parseCoordinatePair()", "file_type": "code", "source_file": "ui.js", "source_location": "L117"}, {"id": "ui_sandcastleui_updateinfo", "label": ".updateInfo()", "file_type": "code", "source_file": "ui.js", "source_location": "L134"}, {"id": "ui_sandcastleui_collectsettings", "label": ".collectSettings()", "file_type": "code", "source_file": "ui.js", "source_location": "L166"}, {"id": "ui_sandcastleui_loadsettingsfromserver", "label": ".loadSettingsFromServer()", "file_type": "code", "source_file": "ui.js", "source_location": "L188"}, {"id": "ui_sandcastleui_savesettings", "label": ".saveSettings()", "file_type": "code", "source_file": "ui.js", "source_location": "L222"}, {"id": "ui_sandcastleui_createfolders", "label": ".createFolders()", "file_type": "code", "source_file": "ui.js", "source_location": "L249"}, {"id": "ui_sandcastleui_renderfolders", "label": ".renderFolders()", "file_type": "code", "source_file": "ui.js", "source_location": "L282"}, {"id": "ui_sandcastleui_startcapture", "label": ".startCapture()", "file_type": "code", "source_file": "ui.js", "source_location": "L296"}], "edges": [{"source": "ui_js", "target": "ui_sandcastleui", "relation": "contains", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L3", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_constructor", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L4", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_initializeelements", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L35", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_attacheventlisteners", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L65", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_log", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L84", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_updatestatus", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L93", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_dmstodecimal", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L98", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_parsecoordinatepair", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L117", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_updateinfo", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L134", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_collectsettings", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L166", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_loadsettingsfromserver", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L188", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_savesettings", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L222", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_createfolders", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L249", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_renderfolders", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L282", "weight": 1.0}, {"source": "ui_sandcastleui", "target": "ui_sandcastleui_startcapture", "relation": "method", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L296", "weight": 1.0}, {"source": "ui_sandcastleui_constructor", "target": "ui_sandcastleui_initializeelements", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L29", "weight": 1.0}, {"source": "ui_sandcastleui_constructor", "target": "ui_sandcastleui_attacheventlisteners", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L30", "weight": 1.0}, {"source": "ui_sandcastleui_constructor", "target": "ui_sandcastleui_loadsettingsfromserver", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L31", "weight": 1.0}, {"source": "ui_sandcastleui_constructor", "target": "ui_sandcastleui_updateinfo", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L32", "weight": 1.0}, {"source": "ui_sandcastleui_updateinfo", "target": "ui_sandcastleui_parsecoordinatepair", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L143", "weight": 1.0}, {"source": "ui_sandcastleui_loadsettingsfromserver", "target": "ui_sandcastleui_collectsettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L214", "weight": 1.0}, {"source": "ui_sandcastleui_loadsettingsfromserver", "target": "ui_sandcastleui_updateinfo", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L215", "weight": 1.0}, {"source": "ui_sandcastleui_loadsettingsfromserver", "target": "ui_sandcastleui_log", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L216", "weight": 1.0}, {"source": "ui_sandcastleui_savesettings", "target": "ui_sandcastleui_collectsettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L223", "weight": 1.0}, {"source": "ui_sandcastleui_savesettings", "target": "ui_sandcastleui_updateinfo", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L224", "weight": 1.0}, {"source": "ui_sandcastleui_savesettings", "target": "ui_sandcastleui_log", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L238", "weight": 1.0}, {"source": "ui_sandcastleui_savesettings", "target": "ui_sandcastleui_updatestatus", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L239", "weight": 1.0}, {"source": "ui_sandcastleui_createfolders", "target": "ui_sandcastleui_collectsettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L250", "weight": 1.0}, {"source": "ui_sandcastleui_createfolders", "target": "ui_sandcastleui_updatestatus", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L251", "weight": 1.0}, {"source": "ui_sandcastleui_createfolders", "target": "ui_sandcastleui_log", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L252", "weight": 1.0}, {"source": "ui_sandcastleui_createfolders", "target": "ui_sandcastleui_renderfolders", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L267", "weight": 1.0}, {"source": "ui_sandcastleui_startcapture", "target": "ui_sandcastleui_collectsettings", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L297", "weight": 1.0}, {"source": "ui_sandcastleui_startcapture", "target": "ui_sandcastleui_updatestatus", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L298", "weight": 1.0}, {"source": "ui_sandcastleui_startcapture", "target": "ui_sandcastleui_log", "relation": "calls", "confidence": "EXTRACTED", "source_file": "ui.js", "source_location": "L299", "weight": 1.0}], "raw_calls": [{"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L36"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L37"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L38"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L39"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L40"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L41"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L42"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L43"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L44"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L45"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L46"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L47"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L48"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L49"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L50"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L51"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L53"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L54"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L55"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L57"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L58"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L59"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L60"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L61"}, {"caller_nid": "ui_sandcastleui_initializeelements", "callee": "getElementById", "source_file": "ui.js", "source_location": "L62"}, {"caller_nid": "ui_sandcastleui_attacheventlisteners", "callee": "addEventListener", "source_file": "ui.js", "source_location": "L66"}, {"caller_nid": "ui_sandcastleui_attacheventlisteners", "callee": "addEventListener", "source_file": "ui.js", "source_location": "L67"}, {"caller_nid": "ui_sandcastleui_attacheventlisteners", "callee": "addEventListener", "source_file": "ui.js", "source_location": "L68"}, {"caller_nid": "ui_sandcastleui_attacheventlisteners", "callee": "forEach", "source_file": "ui.js", "source_location": "L70"}, {"caller_nid": "ui_sandcastleui_log", "callee": "createElement", "source_file": "ui.js", "source_location": "L85"}, {"caller_nid": "ui_sandcastleui_log", "callee": "toLocaleTimeString", "source_file": "ui.js", "source_location": "L87"}, {"caller_nid": "ui_sandcastleui_log", "callee": "appendChild", "source_file": "ui.js", "source_location": "L89"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "match", "source_file": "ui.js", "source_location": "L99"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "trim", "source_file": "ui.js", "source_location": "L99"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "String", "source_file": "ui.js", "source_location": "L99"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "parseInt", "source_file": "ui.js", "source_location": "L104"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "parseInt", "source_file": "ui.js", "source_location": "L105"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "parseFloat", "source_file": "ui.js", "source_location": "L106"}, {"caller_nid": "ui_sandcastleui_dmstodecimal", "callee": "toUpperCase", "source_file": "ui.js", "source_location": "L107"}, {"caller_nid": "ui_sandcastleui_parsecoordinatepair", "callee": "match", "source_file": "ui.js", "source_location": "L118"}, {"caller_nid": "ui_sandcastleui_parsecoordinatepair", "callee": "String", "source_file": "ui.js", "source_location": "L118"}, {"caller_nid": "ui_sandcastleui_parsecoordinatepair", "callee": "map", "source_file": "ui.js", "source_location": "L122"}, {"caller_nid": "ui_sandcastleui_parsecoordinatepair", "callee": "find", "source_file": "ui.js", "source_location": "L126"}, {"caller_nid": "ui_sandcastleui_parsecoordinatepair", "callee": "find", "source_file": "ui.js", "source_location": "L127"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "max", "source_file": "ui.js", "source_location": "L135"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "parseInt", "source_file": "ui.js", "source_location": "L135"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "max", "source_file": "ui.js", "source_location": "L136"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "Number", "source_file": "ui.js", "source_location": "L136"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "min", "source_file": "ui.js", "source_location": "L145"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "max", "source_file": "ui.js", "source_location": "L146"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "min", "source_file": "ui.js", "source_location": "L147"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "max", "source_file": "ui.js", "source_location": "L148"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "cos", "source_file": "ui.js", "source_location": "L150"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "max", "source_file": "ui.js", "source_location": "L151"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "floor", "source_file": "ui.js", "source_location": "L151"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "max", "source_file": "ui.js", "source_location": "L152"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "floor", "source_file": "ui.js", "source_location": "L152"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "ceil", "source_file": "ui.js", "source_location": "L153"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "ceil", "source_file": "ui.js", "source_location": "L154"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "round", "source_file": "ui.js", "source_location": "L159"}, {"caller_nid": "ui_sandcastleui_updateinfo", "callee": "round", "source_file": "ui.js", "source_location": "L159"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "trim", "source_file": "ui.js", "source_location": "L168"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "trim", "source_file": "ui.js", "source_location": "L169"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "trim", "source_file": "ui.js", "source_location": "L170"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "trim", "source_file": "ui.js", "source_location": "L171"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "trim", "source_file": "ui.js", "source_location": "L172"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "trim", "source_file": "ui.js", "source_location": "L173"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "max", "source_file": "ui.js", "source_location": "L174"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "Number", "source_file": "ui.js", "source_location": "L174"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "max", "source_file": "ui.js", "source_location": "L175"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "parseInt", "source_file": "ui.js", "source_location": "L175"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "max", "source_file": "ui.js", "source_location": "L180"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "parseInt", "source_file": "ui.js", "source_location": "L180"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "max", "source_file": "ui.js", "source_location": "L181"}, {"caller_nid": "ui_sandcastleui_collectsettings", "callee": "parseInt", "source_file": "ui.js", "source_location": "L181"}, {"caller_nid": "ui_sandcastleui_loadsettingsfromserver", "callee": "fetch", "source_file": "ui.js", "source_location": "L190"}, {"caller_nid": "ui_sandcastleui_loadsettingsfromserver", "callee": "json", "source_file": "ui.js", "source_location": "L191"}, {"caller_nid": "ui_sandcastleui_savesettings", "callee": "fetch", "source_file": "ui.js", "source_location": "L227"}, {"caller_nid": "ui_sandcastleui_savesettings", "callee": "stringify", "source_file": "ui.js", "source_location": "L232"}, {"caller_nid": "ui_sandcastleui_savesettings", "callee": "json", "source_file": "ui.js", "source_location": "L235"}, {"caller_nid": "ui_sandcastleui_createfolders", "callee": "fetch", "source_file": "ui.js", "source_location": "L255"}, {"caller_nid": "ui_sandcastleui_createfolders", "callee": "stringify", "source_file": "ui.js", "source_location": "L260"}, {"caller_nid": "ui_sandcastleui_createfolders", "callee": "json", "source_file": "ui.js", "source_location": "L263"}, {"caller_nid": "ui_sandcastleui_renderfolders", "callee": "join", "source_file": "ui.js", "source_location": "L288"}, {"caller_nid": "ui_sandcastleui_renderfolders", "callee": "map", "source_file": "ui.js", "source_location": "L288"}, {"caller_nid": "ui_sandcastleui_startcapture", "callee": "fetch", "source_file": "ui.js", "source_location": "L302"}, {"caller_nid": "ui_sandcastleui_startcapture", "callee": "stringify", "source_file": "ui.js", "source_location": "L307"}, {"caller_nid": "ui_sandcastleui_startcapture", "callee": "json", "source_file": "ui.js", "source_location": "L310"}, {"caller_nid": "ui_sandcastleui_startcapture", "callee": "open", "source_file": "ui.js", "source_location": "L315"}, {"caller_nid": "ui_sandcastleui_startcapture", "callee": "open", "source_file": "ui.js", "source_location": "L316"}]}
\ No newline at end of file
diff --git a/graphify-out/cost.json b/graphify-out/cost.json
new file mode 100644
index 0000000..b6c61bd
--- /dev/null
+++ b/graphify-out/cost.json
@@ -0,0 +1,12 @@
+{
+ "runs": [
+ {
+ "date": "2026-04-15T06:49:58.200070+00:00",
+ "input_tokens": 0,
+ "output_tokens": 0,
+ "files": 5
+ }
+ ],
+ "total_input_tokens": 0,
+ "total_output_tokens": 0
+}
\ No newline at end of file
diff --git a/graphify-out/graph.html b/graphify-out/graph.html
new file mode 100644
index 0000000..4d73fb9
--- /dev/null
+++ b/graphify-out/graph.html
@@ -0,0 +1,257 @@
+
+
+
+
+graphify - graphify-out/graph.html
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/graphify-out/graph.json b/graphify-out/graph.json
new file mode 100644
index 0000000..1ad2bb5
--- /dev/null
+++ b/graphify-out/graph.json
@@ -0,0 +1,2319 @@
+{
+ "directed": false,
+ "multigraph": false,
+ "graph": {
+ "hyperedges": [
+ {
+ "id": "hyper_capture_flow",
+ "label": "Capture pipeline",
+ "nodes": [
+ "readme_control_panel",
+ "readme_capture_html",
+ "readme_mapview_html",
+ "readme_broadcastchannel",
+ "readme_server_js"
+ ],
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441"
+ },
+ {
+ "id": "hyper_trajectory_schema",
+ "label": "Trajectory schema fields",
+ "nodes": [
+ "readme_camera_frames",
+ "readme_ecef_position",
+ "readme_rotation",
+ "readme_coordinate",
+ "readme_fov_vertical"
+ ],
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)"
+ },
+ {
+ "id": "hyper_output_layout",
+ "label": "UAV-GeoLoc output layout",
+ "nodes": [
+ "readme_output_tree",
+ "readme_db_crop",
+ "readme_db_position_txt",
+ "readme_drone_frame",
+ "readme_trajectory",
+ "readme_region_txt"
+ ],
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430"
+ }
+ ]
+ },
+ "nodes": [
+ {
+ "label": "app.js",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L1",
+ "id": "app_js",
+ "community": 4,
+ "norm_label": "app.js"
+ },
+ {
+ "label": "dmsToDecimal()",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L51",
+ "id": "app_dmstodecimal",
+ "community": 4,
+ "norm_label": "dmstodecimal()"
+ },
+ {
+ "label": "metersToLat()",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L74",
+ "id": "app_meterstolat",
+ "community": 4,
+ "norm_label": "meterstolat()"
+ },
+ {
+ "label": "metersToLon()",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L78",
+ "id": "app_meterstolon",
+ "community": 4,
+ "norm_label": "meterstolon()"
+ },
+ {
+ "label": "saveImage()",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L82",
+ "id": "app_saveimage",
+ "community": 4,
+ "norm_label": "saveimage()"
+ },
+ {
+ "label": "downloadFile()",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L94",
+ "id": "app_downloadfile",
+ "community": 4,
+ "norm_label": "downloadfile()"
+ },
+ {
+ "label": "captureNextTile()",
+ "file_type": "code",
+ "source_file": "app.js",
+ "source_location": "L145",
+ "id": "app_capturenexttile",
+ "community": 4,
+ "norm_label": "capturenexttile()"
+ },
+ {
+ "label": "server.js",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L1",
+ "id": "server_js",
+ "community": 3,
+ "norm_label": "server.js"
+ },
+ {
+ "label": "metersToLat()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L17",
+ "id": "server_meterstolat",
+ "community": 3,
+ "norm_label": "meterstolat()"
+ },
+ {
+ "label": "metersToLon()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L21",
+ "id": "server_meterstolon",
+ "community": 3,
+ "norm_label": "meterstolon()"
+ },
+ {
+ "label": "dmsToDecimal()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L25",
+ "id": "server_dmstodecimal",
+ "community": 3,
+ "norm_label": "dmstodecimal()"
+ },
+ {
+ "label": "parseCoordinatePair()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L44",
+ "id": "server_parsecoordinatepair",
+ "community": 3,
+ "norm_label": "parsecoordinatepair()"
+ },
+ {
+ "label": "getBoundsFromSettings()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L65",
+ "id": "server_getboundsfromsettings",
+ "community": 3,
+ "norm_label": "getboundsfromsettings()"
+ },
+ {
+ "label": "decimalToDMS()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L84",
+ "id": "server_decimaltodms",
+ "community": 3,
+ "norm_label": "decimaltodms()"
+ },
+ {
+ "label": "normalizeSettings()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L103",
+ "id": "server_normalizesettings",
+ "community": 3,
+ "norm_label": "normalizesettings()"
+ },
+ {
+ "label": "computeGrid()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L135",
+ "id": "server_computegrid",
+ "community": 3,
+ "norm_label": "computegrid()"
+ },
+ {
+ "label": "generateCoordinates()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L144",
+ "id": "server_generatecoordinates",
+ "community": 3,
+ "norm_label": "generatecoordinates()"
+ },
+ {
+ "label": "dataUrlToBuffer()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L173",
+ "id": "server_dataurltobuffer",
+ "community": 3,
+ "norm_label": "dataurltobuffer()"
+ },
+ {
+ "label": "sanitizeNameSegment()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L186",
+ "id": "server_sanitizenamesegment",
+ "community": 3,
+ "norm_label": "sanitizenamesegment()"
+ },
+ {
+ "label": "getScenePath()",
+ "file_type": "code",
+ "source_file": "server.js",
+ "source_location": "L194",
+ "id": "server_getscenepath",
+ "community": 3,
+ "norm_label": "getscenepath()"
+ },
+ {
+ "label": "ui.js",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L1",
+ "id": "ui_js",
+ "community": 1,
+ "norm_label": "ui.js"
+ },
+ {
+ "label": "SandCastleUI",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L3",
+ "id": "ui_sandcastleui",
+ "community": 1,
+ "norm_label": "sandcastleui"
+ },
+ {
+ "label": ".constructor()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L4",
+ "id": "ui_sandcastleui_constructor",
+ "community": 1,
+ "norm_label": ".constructor()"
+ },
+ {
+ "label": ".initializeElements()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L35",
+ "id": "ui_sandcastleui_initializeelements",
+ "community": 1,
+ "norm_label": ".initializeelements()"
+ },
+ {
+ "label": ".attachEventListeners()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L65",
+ "id": "ui_sandcastleui_attacheventlisteners",
+ "community": 1,
+ "norm_label": ".attacheventlisteners()"
+ },
+ {
+ "label": ".log()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L84",
+ "id": "ui_sandcastleui_log",
+ "community": 1,
+ "norm_label": ".log()"
+ },
+ {
+ "label": ".updateStatus()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L93",
+ "id": "ui_sandcastleui_updatestatus",
+ "community": 1,
+ "norm_label": ".updatestatus()"
+ },
+ {
+ "label": ".dmsToDecimal()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L98",
+ "id": "ui_sandcastleui_dmstodecimal",
+ "community": 1,
+ "norm_label": ".dmstodecimal()"
+ },
+ {
+ "label": ".parseCoordinatePair()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L117",
+ "id": "ui_sandcastleui_parsecoordinatepair",
+ "community": 1,
+ "norm_label": ".parsecoordinatepair()"
+ },
+ {
+ "label": ".updateInfo()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L134",
+ "id": "ui_sandcastleui_updateinfo",
+ "community": 1,
+ "norm_label": ".updateinfo()"
+ },
+ {
+ "label": ".collectSettings()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L166",
+ "id": "ui_sandcastleui_collectsettings",
+ "community": 1,
+ "norm_label": ".collectsettings()"
+ },
+ {
+ "label": ".loadSettingsFromServer()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L188",
+ "id": "ui_sandcastleui_loadsettingsfromserver",
+ "community": 1,
+ "norm_label": ".loadsettingsfromserver()"
+ },
+ {
+ "label": ".saveSettings()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L222",
+ "id": "ui_sandcastleui_savesettings",
+ "community": 1,
+ "norm_label": ".savesettings()"
+ },
+ {
+ "label": ".createFolders()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L249",
+ "id": "ui_sandcastleui_createfolders",
+ "community": 1,
+ "norm_label": ".createfolders()"
+ },
+ {
+ "label": ".renderFolders()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L282",
+ "id": "ui_sandcastleui_renderfolders",
+ "community": 1,
+ "norm_label": ".renderfolders()"
+ },
+ {
+ "label": ".startCapture()",
+ "file_type": "code",
+ "source_file": "ui.js",
+ "source_location": "L296",
+ "id": "ui_sandcastleui_startcapture",
+ "community": 1,
+ "norm_label": ".startcapture()"
+ },
+ {
+ "label": "SandCastle",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "source_url": null,
+ "captured_at": null,
+ "author": "\u041c\u043e\u0440\u043e\u0437 \u0415.\u0421. / \u041b\u0418\u0421\u0410\u0414",
+ "contributor": null,
+ "id": "readme_sandcastle",
+ "community": 2,
+ "norm_label": "sandcastle"
+ },
+ {
+ "label": "UAV-GeoLoc",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_uav_geoloc",
+ "community": 2,
+ "norm_label": "uav-geoloc"
+ },
+ {
+ "label": "Google Photorealistic 3D Tiles",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_google_3d_tiles",
+ "community": 2,
+ "norm_label": "google photorealistic 3d tiles"
+ },
+ {
+ "label": "Cesium",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_cesium",
+ "community": 2,
+ "norm_label": "cesium"
+ },
+ {
+ "label": "Cesium Ion Token (asset 2275207)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_cesium_ion_token",
+ "community": 2,
+ "norm_label": "cesium ion token (asset 2275207)"
+ },
+ {
+ "label": "DB crop (ortho crop)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_db_crop",
+ "community": 5,
+ "norm_label": "db crop (ortho crop)"
+ },
+ {
+ "label": "Drone frame (query)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_drone_frame",
+ "community": 5,
+ "norm_label": "drone frame (query)"
+ },
+ {
+ "label": "Camera trajectory JSON",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_trajectory",
+ "community": 2,
+ "norm_label": "camera trajectory json"
+ },
+ {
+ "label": "Control Panel (index.html)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_control_panel",
+ "community": 0,
+ "norm_label": "control panel (index.html)"
+ },
+ {
+ "label": "capture.html (Cesium Viewer)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_capture_html",
+ "community": 2,
+ "norm_label": "capture.html (cesium viewer)"
+ },
+ {
+ "label": "mapview.html (Leaflet map)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_mapview_html",
+ "community": 2,
+ "norm_label": "mapview.html (leaflet map)"
+ },
+ {
+ "label": "server.js (Express backend)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_server_js",
+ "community": 0,
+ "norm_label": "server.js (express backend)"
+ },
+ {
+ "label": "ui.js (UI controller)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_ui_js",
+ "community": 0,
+ "norm_label": "ui.js (ui controller)"
+ },
+ {
+ "label": "start.bat",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_start_bat",
+ "community": 0,
+ "norm_label": "start.bat"
+ },
+ {
+ "label": "package.json",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_package_json",
+ "community": 0,
+ "norm_label": "package.json"
+ },
+ {
+ "label": "settings.json",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_settings_json",
+ "community": 0,
+ "norm_label": "settings.json"
+ },
+ {
+ "label": "output/ directory tree",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_output_tree",
+ "community": 5,
+ "norm_label": "output/ directory tree"
+ },
+ {
+ "label": "db_postion.txt",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_db_position_txt",
+ "community": 5,
+ "norm_label": "db_postion.txt"
+ },
+ {
+ "label": "{region}.txt (bounds)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_region_txt",
+ "community": 0,
+ "norm_label": "{region}.txt (bounds)"
+ },
+ {
+ "label": "Dataset/Country/City/Region tree",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_dataset_country_city_region",
+ "community": 5,
+ "norm_label": "dataset/country/city/region tree"
+ },
+ {
+ "label": "BroadcastChannel('sandcastle-map')",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u041e\u0431\u043c\u0435\u043d \u043e\u043a\u043d\u0430\u043c\u0438",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_broadcastchannel",
+ "community": 2,
+ "norm_label": "broadcastchannel('sandcastle-map')"
+ },
+ {
+ "label": "DMS coordinates",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b DMS",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_dms_coords",
+ "community": 0,
+ "norm_label": "dms coordinates"
+ },
+ {
+ "label": "ECEF position (Cartesian3.fromDegrees)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_ecef_position",
+ "community": 2,
+ "norm_label": "ecef position (cartesian3.fromdegrees)"
+ },
+ {
+ "label": "cameraFrames",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_camera_frames",
+ "community": 2,
+ "norm_label": "cameraframes"
+ },
+ {
+ "label": "fovVertical",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_fov_vertical",
+ "community": 2,
+ "norm_label": "fovvertical"
+ },
+ {
+ "label": "rotation (pitch/heading/roll)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_rotation",
+ "community": 2,
+ "norm_label": "rotation (pitch/heading/roll)"
+ },
+ {
+ "label": "coordinate (geodetic)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_coordinate",
+ "community": 2,
+ "norm_label": "coordinate (geodetic)"
+ },
+ {
+ "label": "GET /api/get-settings",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_get_settings",
+ "community": 0,
+ "norm_label": "get /api/get-settings"
+ },
+ {
+ "label": "POST /api/save-settings",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_save_settings",
+ "community": 0,
+ "norm_label": "post /api/save-settings"
+ },
+ {
+ "label": "POST /api/create-folders",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_create_folders",
+ "community": 0,
+ "norm_label": "post /api/create-folders"
+ },
+ {
+ "label": "POST /api/start-capture",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_start_capture",
+ "community": 0,
+ "norm_label": "post /api/start-capture"
+ },
+ {
+ "label": "POST /api/save-db-crop",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_save_db_crop",
+ "community": 5,
+ "norm_label": "post /api/save-db-crop"
+ },
+ {
+ "label": "POST /api/save-drone-frame",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_save_drone_frame",
+ "community": 5,
+ "norm_label": "post /api/save-drone-frame"
+ },
+ {
+ "label": "POST /api/save-trajectory",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_api_save_trajectory",
+ "community": 2,
+ "norm_label": "post /api/save-trajectory"
+ },
+ {
+ "label": "Leaflet",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_leaflet",
+ "community": 2,
+ "norm_label": "leaflet"
+ },
+ {
+ "label": "Express",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_express",
+ "community": 0,
+ "norm_label": "express"
+ },
+ {
+ "label": "Node.js 14+",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_nodejs",
+ "community": 0,
+ "norm_label": "node.js 14+"
+ },
+ {
+ "label": "Grid step (meters)",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_grid_step",
+ "community": 0,
+ "norm_label": "grid step (meters)"
+ },
+ {
+ "label": "Heights 100/125/150",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_heights",
+ "community": 0,
+ "norm_label": "heights 100/125/150"
+ },
+ {
+ "label": "Skip empty zones",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_skip_empty_zones",
+ "community": 0,
+ "norm_label": "skip empty zones"
+ },
+ {
+ "label": "Tile preload",
+ "file_type": "document",
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "source_url": null,
+ "captured_at": null,
+ "author": null,
+ "contributor": null,
+ "id": "readme_tile_preload",
+ "community": 0,
+ "norm_label": "tile preload"
+ }
+ ],
+ "links": [
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L51",
+ "weight": 1.0,
+ "_src": "app_js",
+ "_tgt": "app_dmstodecimal",
+ "source": "app_js",
+ "target": "app_dmstodecimal",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L74",
+ "weight": 1.0,
+ "_src": "app_js",
+ "_tgt": "app_meterstolat",
+ "source": "app_js",
+ "target": "app_meterstolat",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L78",
+ "weight": 1.0,
+ "_src": "app_js",
+ "_tgt": "app_meterstolon",
+ "source": "app_js",
+ "target": "app_meterstolon",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L82",
+ "weight": 1.0,
+ "_src": "app_js",
+ "_tgt": "app_saveimage",
+ "source": "app_js",
+ "target": "app_saveimage",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L94",
+ "weight": 1.0,
+ "_src": "app_js",
+ "_tgt": "app_downloadfile",
+ "source": "app_js",
+ "target": "app_downloadfile",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L145",
+ "weight": 1.0,
+ "_src": "app_js",
+ "_tgt": "app_capturenexttile",
+ "source": "app_js",
+ "target": "app_capturenexttile",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L153",
+ "weight": 1.0,
+ "_src": "app_capturenexttile",
+ "_tgt": "app_meterstolat",
+ "source": "app_meterstolat",
+ "target": "app_capturenexttile",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L154",
+ "weight": 1.0,
+ "_src": "app_capturenexttile",
+ "_tgt": "app_meterstolon",
+ "source": "app_meterstolon",
+ "target": "app_capturenexttile",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "app.js",
+ "source_location": "L156",
+ "weight": 1.0,
+ "_src": "app_capturenexttile",
+ "_tgt": "app_downloadfile",
+ "source": "app_downloadfile",
+ "target": "app_capturenexttile",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L17",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_meterstolat",
+ "source": "server_js",
+ "target": "server_meterstolat",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L21",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_meterstolon",
+ "source": "server_js",
+ "target": "server_meterstolon",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L25",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_dmstodecimal",
+ "source": "server_js",
+ "target": "server_dmstodecimal",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L44",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_parsecoordinatepair",
+ "source": "server_js",
+ "target": "server_parsecoordinatepair",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L65",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_getboundsfromsettings",
+ "source": "server_js",
+ "target": "server_getboundsfromsettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L84",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_decimaltodms",
+ "source": "server_js",
+ "target": "server_decimaltodms",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L103",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_normalizesettings",
+ "source": "server_js",
+ "target": "server_normalizesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L135",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_computegrid",
+ "source": "server_js",
+ "target": "server_computegrid",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L144",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_generatecoordinates",
+ "source": "server_js",
+ "target": "server_generatecoordinates",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L173",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_dataurltobuffer",
+ "source": "server_js",
+ "target": "server_dataurltobuffer",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L186",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_sanitizenamesegment",
+ "source": "server_js",
+ "target": "server_sanitizenamesegment",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L194",
+ "weight": 1.0,
+ "_src": "server_js",
+ "_tgt": "server_getscenepath",
+ "source": "server_js",
+ "target": "server_getscenepath",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L155",
+ "weight": 1.0,
+ "_src": "server_generatecoordinates",
+ "_tgt": "server_meterstolat",
+ "source": "server_meterstolat",
+ "target": "server_generatecoordinates",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L156",
+ "weight": 1.0,
+ "_src": "server_generatecoordinates",
+ "_tgt": "server_meterstolon",
+ "source": "server_meterstolon",
+ "target": "server_generatecoordinates",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L73",
+ "weight": 1.0,
+ "_src": "server_getboundsfromsettings",
+ "_tgt": "server_parsecoordinatepair",
+ "source": "server_parsecoordinatepair",
+ "target": "server_getboundsfromsettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L131",
+ "weight": 1.0,
+ "_src": "server_normalizesettings",
+ "_tgt": "server_getboundsfromsettings",
+ "source": "server_getboundsfromsettings",
+ "target": "server_normalizesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L145",
+ "weight": 1.0,
+ "_src": "server_generatecoordinates",
+ "_tgt": "server_getboundsfromsettings",
+ "source": "server_getboundsfromsettings",
+ "target": "server_generatecoordinates",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L164",
+ "weight": 1.0,
+ "_src": "server_generatecoordinates",
+ "_tgt": "server_decimaltodms",
+ "source": "server_decimaltodms",
+ "target": "server_generatecoordinates",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L148",
+ "weight": 1.0,
+ "_src": "server_generatecoordinates",
+ "_tgt": "server_computegrid",
+ "source": "server_computegrid",
+ "target": "server_generatecoordinates",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "server.js",
+ "source_location": "L197",
+ "weight": 1.0,
+ "_src": "server_getscenepath",
+ "_tgt": "server_sanitizenamesegment",
+ "source": "server_sanitizenamesegment",
+ "target": "server_getscenepath",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L3",
+ "weight": 1.0,
+ "_src": "ui_js",
+ "_tgt": "ui_sandcastleui",
+ "source": "ui_js",
+ "target": "ui_sandcastleui",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L4",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_constructor",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_constructor",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L35",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_initializeelements",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_initializeelements",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L65",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_attacheventlisteners",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_attacheventlisteners",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L84",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_log",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_log",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L93",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_updatestatus",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_updatestatus",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L98",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_dmstodecimal",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_dmstodecimal",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L117",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_parsecoordinatepair",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_parsecoordinatepair",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L134",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_updateinfo",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_updateinfo",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L166",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_collectsettings",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_collectsettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L188",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_loadsettingsfromserver",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_loadsettingsfromserver",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L222",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_savesettings",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_savesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L249",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_createfolders",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_createfolders",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L282",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_renderfolders",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_renderfolders",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "method",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L296",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui",
+ "_tgt": "ui_sandcastleui_startcapture",
+ "source": "ui_sandcastleui",
+ "target": "ui_sandcastleui_startcapture",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L29",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_constructor",
+ "_tgt": "ui_sandcastleui_initializeelements",
+ "source": "ui_sandcastleui_constructor",
+ "target": "ui_sandcastleui_initializeelements",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L30",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_constructor",
+ "_tgt": "ui_sandcastleui_attacheventlisteners",
+ "source": "ui_sandcastleui_constructor",
+ "target": "ui_sandcastleui_attacheventlisteners",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L31",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_constructor",
+ "_tgt": "ui_sandcastleui_loadsettingsfromserver",
+ "source": "ui_sandcastleui_constructor",
+ "target": "ui_sandcastleui_loadsettingsfromserver",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L32",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_constructor",
+ "_tgt": "ui_sandcastleui_updateinfo",
+ "source": "ui_sandcastleui_constructor",
+ "target": "ui_sandcastleui_updateinfo",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L216",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_loadsettingsfromserver",
+ "_tgt": "ui_sandcastleui_log",
+ "source": "ui_sandcastleui_log",
+ "target": "ui_sandcastleui_loadsettingsfromserver",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L238",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_savesettings",
+ "_tgt": "ui_sandcastleui_log",
+ "source": "ui_sandcastleui_log",
+ "target": "ui_sandcastleui_savesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L252",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_createfolders",
+ "_tgt": "ui_sandcastleui_log",
+ "source": "ui_sandcastleui_log",
+ "target": "ui_sandcastleui_createfolders",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L299",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_startcapture",
+ "_tgt": "ui_sandcastleui_log",
+ "source": "ui_sandcastleui_log",
+ "target": "ui_sandcastleui_startcapture",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L239",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_savesettings",
+ "_tgt": "ui_sandcastleui_updatestatus",
+ "source": "ui_sandcastleui_updatestatus",
+ "target": "ui_sandcastleui_savesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L251",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_createfolders",
+ "_tgt": "ui_sandcastleui_updatestatus",
+ "source": "ui_sandcastleui_updatestatus",
+ "target": "ui_sandcastleui_createfolders",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L298",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_startcapture",
+ "_tgt": "ui_sandcastleui_updatestatus",
+ "source": "ui_sandcastleui_updatestatus",
+ "target": "ui_sandcastleui_startcapture",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L143",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_updateinfo",
+ "_tgt": "ui_sandcastleui_parsecoordinatepair",
+ "source": "ui_sandcastleui_parsecoordinatepair",
+ "target": "ui_sandcastleui_updateinfo",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L215",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_loadsettingsfromserver",
+ "_tgt": "ui_sandcastleui_updateinfo",
+ "source": "ui_sandcastleui_updateinfo",
+ "target": "ui_sandcastleui_loadsettingsfromserver",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L224",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_savesettings",
+ "_tgt": "ui_sandcastleui_updateinfo",
+ "source": "ui_sandcastleui_updateinfo",
+ "target": "ui_sandcastleui_savesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L214",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_loadsettingsfromserver",
+ "_tgt": "ui_sandcastleui_collectsettings",
+ "source": "ui_sandcastleui_collectsettings",
+ "target": "ui_sandcastleui_loadsettingsfromserver",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L223",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_savesettings",
+ "_tgt": "ui_sandcastleui_collectsettings",
+ "source": "ui_sandcastleui_collectsettings",
+ "target": "ui_sandcastleui_savesettings",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L250",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_createfolders",
+ "_tgt": "ui_sandcastleui_collectsettings",
+ "source": "ui_sandcastleui_collectsettings",
+ "target": "ui_sandcastleui_createfolders",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L297",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_startcapture",
+ "_tgt": "ui_sandcastleui_collectsettings",
+ "source": "ui_sandcastleui_collectsettings",
+ "target": "ui_sandcastleui_startcapture",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "calls",
+ "confidence": "EXTRACTED",
+ "source_file": "ui.js",
+ "source_location": "L267",
+ "weight": 1.0,
+ "_src": "ui_sandcastleui_createfolders",
+ "_tgt": "ui_sandcastleui_renderfolders",
+ "source": "ui_sandcastleui_createfolders",
+ "target": "ui_sandcastleui_renderfolders",
+ "confidence_score": 1.0
+ },
+ {
+ "relation": "produces_dataset_format",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "weight": 1.0,
+ "_src": "readme_sandcastle",
+ "_tgt": "readme_uav_geoloc",
+ "source": "readme_sandcastle",
+ "target": "readme_uav_geoloc"
+ },
+ {
+ "relation": "captures",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "weight": 1.0,
+ "_src": "readme_sandcastle",
+ "_tgt": "readme_google_3d_tiles",
+ "source": "readme_sandcastle",
+ "target": "readme_google_3d_tiles"
+ },
+ {
+ "relation": "uses",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "weight": 1.0,
+ "_src": "readme_sandcastle",
+ "_tgt": "readme_cesium",
+ "source": "readme_sandcastle",
+ "target": "readme_cesium"
+ },
+ {
+ "relation": "conforms_to_format",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_trajectory",
+ "_tgt": "readme_uav_geoloc",
+ "source": "readme_uav_geoloc",
+ "target": "readme_trajectory"
+ },
+ {
+ "relation": "renders",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "weight": 1.0,
+ "_src": "readme_cesium",
+ "_tgt": "readme_google_3d_tiles",
+ "source": "readme_google_3d_tiles",
+ "target": "readme_cesium"
+ },
+ {
+ "relation": "embeds",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_capture_html",
+ "_tgt": "readme_cesium",
+ "source": "readme_cesium",
+ "target": "readme_capture_html"
+ },
+ {
+ "relation": "computed_via",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_ecef_position",
+ "_tgt": "readme_cesium",
+ "source": "readme_cesium",
+ "target": "readme_ecef_position"
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f",
+ "weight": 1.0,
+ "_src": "readme_capture_html",
+ "_tgt": "readme_cesium_ion_token",
+ "source": "readme_cesium_ion_token",
+ "target": "readme_capture_html"
+ },
+ {
+ "relation": "writes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_save_db_crop",
+ "_tgt": "readme_db_crop",
+ "source": "readme_db_crop",
+ "target": "readme_api_save_db_crop"
+ },
+ {
+ "relation": "paired_with",
+ "confidence": "INFERRED",
+ "confidence_score": 0.85,
+ "source_file": "README.md",
+ "source_location": "# SandCastle",
+ "weight": 1.0,
+ "_src": "readme_db_crop",
+ "_tgt": "readme_drone_frame",
+ "source": "readme_db_crop",
+ "target": "readme_drone_frame"
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_output_tree",
+ "_tgt": "readme_db_crop",
+ "source": "readme_db_crop",
+ "target": "readme_output_tree"
+ },
+ {
+ "relation": "writes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_save_drone_frame",
+ "_tgt": "readme_drone_frame",
+ "source": "readme_drone_frame",
+ "target": "readme_api_save_drone_frame"
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_output_tree",
+ "_tgt": "readme_drone_frame",
+ "source": "readme_drone_frame",
+ "target": "readme_output_tree"
+ },
+ {
+ "relation": "writes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_save_trajectory",
+ "_tgt": "readme_trajectory",
+ "source": "readme_trajectory",
+ "target": "readme_api_save_trajectory"
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_trajectory",
+ "_tgt": "readme_camera_frames",
+ "source": "readme_trajectory",
+ "target": "readme_camera_frames"
+ },
+ {
+ "relation": "contains",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_output_tree",
+ "_tgt": "readme_trajectory",
+ "source": "readme_trajectory",
+ "target": "readme_output_tree"
+ },
+ {
+ "relation": "controlled_by",
+ "confidence": "INFERRED",
+ "confidence_score": 0.85,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_ui_js",
+ "source": "readme_control_panel",
+ "target": "readme_ui_js"
+ },
+ {
+ "relation": "calls",
+ "confidence": "INFERRED",
+ "confidence_score": 0.85,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_api_save_settings",
+ "source": "readme_control_panel",
+ "target": "readme_api_save_settings"
+ },
+ {
+ "relation": "calls",
+ "confidence": "INFERRED",
+ "confidence_score": 0.85,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_api_create_folders",
+ "source": "readme_control_panel",
+ "target": "readme_api_create_folders"
+ },
+ {
+ "relation": "calls",
+ "confidence": "INFERRED",
+ "confidence_score": 0.85,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_api_start_capture",
+ "source": "readme_control_panel",
+ "target": "readme_api_start_capture"
+ },
+ {
+ "relation": "accepts",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b DMS",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_dms_coords",
+ "source": "readme_control_panel",
+ "target": "readme_dms_coords"
+ },
+ {
+ "relation": "configures",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_grid_step",
+ "source": "readme_control_panel",
+ "target": "readme_grid_step"
+ },
+ {
+ "relation": "configures",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_heights",
+ "source": "readme_control_panel",
+ "target": "readme_heights"
+ },
+ {
+ "relation": "configures",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_skip_empty_zones",
+ "source": "readme_control_panel",
+ "target": "readme_skip_empty_zones"
+ },
+ {
+ "relation": "configures",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441",
+ "weight": 1.0,
+ "_src": "readme_control_panel",
+ "_tgt": "readme_tile_preload",
+ "source": "readme_control_panel",
+ "target": "readme_tile_preload"
+ },
+ {
+ "relation": "communicates_via",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u041e\u0431\u043c\u0435\u043d \u043e\u043a\u043d\u0430\u043c\u0438",
+ "weight": 1.0,
+ "_src": "readme_capture_html",
+ "_tgt": "readme_mapview_html",
+ "source": "readme_capture_html",
+ "target": "readme_mapview_html"
+ },
+ {
+ "relation": "uses",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u041e\u0431\u043c\u0435\u043d \u043e\u043a\u043d\u0430\u043c\u0438",
+ "weight": 1.0,
+ "_src": "readme_capture_html",
+ "_tgt": "readme_broadcastchannel",
+ "source": "readme_capture_html",
+ "target": "readme_broadcastchannel"
+ },
+ {
+ "relation": "uses",
+ "confidence": "INFERRED",
+ "confidence_score": 0.8,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_mapview_html",
+ "_tgt": "readme_leaflet",
+ "source": "readme_mapview_html",
+ "target": "readme_leaflet"
+ },
+ {
+ "relation": "uses",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u041e\u0431\u043c\u0435\u043d \u043e\u043a\u043d\u0430\u043c\u0438",
+ "weight": 1.0,
+ "_src": "readme_mapview_html",
+ "_tgt": "readme_broadcastchannel",
+ "source": "readme_mapview_html",
+ "target": "readme_broadcastchannel"
+ },
+ {
+ "relation": "uses",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_express",
+ "source": "readme_server_js",
+ "target": "readme_express"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_get_settings",
+ "source": "readme_server_js",
+ "target": "readme_api_get_settings"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_save_settings",
+ "source": "readme_server_js",
+ "target": "readme_api_save_settings"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_create_folders",
+ "source": "readme_server_js",
+ "target": "readme_api_create_folders"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_start_capture",
+ "source": "readme_server_js",
+ "target": "readme_api_start_capture"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_save_db_crop",
+ "source": "readme_server_js",
+ "target": "readme_api_save_db_crop"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_save_drone_frame",
+ "source": "readme_server_js",
+ "target": "readme_api_save_drone_frame"
+ },
+ {
+ "relation": "exposes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_server_js",
+ "_tgt": "readme_api_save_trajectory",
+ "source": "readme_server_js",
+ "target": "readme_api_save_trajectory"
+ },
+ {
+ "relation": "starts",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442",
+ "weight": 1.0,
+ "_src": "readme_start_bat",
+ "_tgt": "readme_server_js",
+ "source": "readme_server_js",
+ "target": "readme_start_bat"
+ },
+ {
+ "relation": "requires",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0422\u0440\u0435\u0431\u043e\u0432\u0430\u043d\u0438\u044f",
+ "weight": 1.0,
+ "_src": "readme_start_bat",
+ "_tgt": "readme_nodejs",
+ "source": "readme_start_bat",
+ "target": "readme_nodejs"
+ },
+ {
+ "relation": "declares_runtime",
+ "confidence": "INFERRED",
+ "confidence_score": 0.8,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_package_json",
+ "_tgt": "readme_nodejs",
+ "source": "readme_package_json",
+ "target": "readme_nodejs"
+ },
+ {
+ "relation": "writes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_save_settings",
+ "_tgt": "readme_settings_json",
+ "source": "readme_settings_json",
+ "target": "readme_api_save_settings"
+ },
+ {
+ "relation": "reads",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_get_settings",
+ "_tgt": "readme_settings_json",
+ "source": "readme_settings_json",
+ "target": "readme_api_get_settings"
+ },
+ {
+ "relation": "creates",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_create_folders",
+ "_tgt": "readme_output_tree",
+ "source": "readme_output_tree",
+ "target": "readme_api_create_folders"
+ },
+ {
+ "relation": "structured_by",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430",
+ "weight": 1.0,
+ "_src": "readme_output_tree",
+ "_tgt": "readme_dataset_country_city_region",
+ "source": "readme_output_tree",
+ "target": "readme_dataset_country_city_region"
+ },
+ {
+ "relation": "appends_to",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_save_db_crop",
+ "_tgt": "readme_db_position_txt",
+ "source": "readme_db_position_txt",
+ "target": "readme_api_save_db_crop"
+ },
+ {
+ "relation": "writes",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## API \u0441\u0435\u0440\u0432\u0435\u0440\u0430",
+ "weight": 1.0,
+ "_src": "readme_api_create_folders",
+ "_tgt": "readme_region_txt",
+ "source": "readme_region_txt",
+ "target": "readme_api_create_folders"
+ },
+ {
+ "relation": "includes_field",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_camera_frames",
+ "_tgt": "readme_ecef_position",
+ "source": "readme_ecef_position",
+ "target": "readme_camera_frames"
+ },
+ {
+ "relation": "includes_field",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_camera_frames",
+ "_tgt": "readme_rotation",
+ "source": "readme_camera_frames",
+ "target": "readme_rotation"
+ },
+ {
+ "relation": "includes_field",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_camera_frames",
+ "_tgt": "readme_coordinate",
+ "source": "readme_camera_frames",
+ "target": "readme_coordinate"
+ },
+ {
+ "relation": "includes_field",
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)",
+ "weight": 1.0,
+ "_src": "readme_camera_frames",
+ "_tgt": "readme_fov_vertical",
+ "source": "readme_camera_frames",
+ "target": "readme_fov_vertical"
+ }
+ ],
+ "hyperedges": [
+ {
+ "id": "hyper_capture_flow",
+ "label": "Capture pipeline",
+ "nodes": [
+ "readme_control_panel",
+ "readme_capture_html",
+ "readme_mapview_html",
+ "readme_broadcastchannel",
+ "readme_server_js"
+ ],
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0420\u0430\u0431\u043e\u0447\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441"
+ },
+ {
+ "id": "hyper_trajectory_schema",
+ "label": "Trajectory schema fields",
+ "nodes": [
+ "readme_camera_frames",
+ "readme_ecef_position",
+ "readme_rotation",
+ "readme_coordinate",
+ "readme_fov_vertical"
+ ],
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0424\u043e\u0440\u043c\u0430\u0442 \u0442\u0440\u0430\u0435\u043a\u0442\u043e\u0440\u0438\u0438 (UAV-GeoLoc)"
+ },
+ {
+ "id": "hyper_output_layout",
+ "label": "UAV-GeoLoc output layout",
+ "nodes": [
+ "readme_output_tree",
+ "readme_db_crop",
+ "readme_db_position_txt",
+ "readme_drone_frame",
+ "readme_trajectory",
+ "readme_region_txt"
+ ],
+ "confidence": "EXTRACTED",
+ "confidence_score": 1.0,
+ "source_file": "README.md",
+ "source_location": "## \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/graphify-out/manifest.json b/graphify-out/manifest.json
new file mode 100644
index 0000000..64357e3
--- /dev/null
+++ b/graphify-out/manifest.json
@@ -0,0 +1,7 @@
+{
+ "app.js": 1775217358.0,
+ "server.js": 1776149190.0,
+ "ui.js": 1776234151.967281,
+ "README.md": 1776234555.7837043,
+ "UAV-GeoLoc_Dataset_Analysis.pdf": 1776148726.9799173
+}
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..b40c549
--- /dev/null
+++ b/index.html
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+ Настройка координат
+
+
+
+
+
+
+
+
+
+
+
Настройки
+
+
+
+
+
+
+ Точка 1 (широта и долгота, DMS)
+
+ Первый угол прямоугольника
+
+
+
+ Точка 2 (широта и долгота, DMS)
+
+ Противоположный угол прямоугольника
+
+
+
+
+
+
DB (gallery / satellite)
+
+
+
+
+
+
+
+
+
+
+
+
+ Пропускать пустые зоны
+
+ Не сохранять области без 3D контента
+
+
+
+ Сохранить настройки
+ Создать папки
+ Запустить захват
+
+
+
+
Шпаргалка (исходные данные)
+
Дроновые виды (query): Платформа Синтетический рендеринг (Google Earth Studio 3D). Тип съёмки RGB, top-down (камера вертикально вниз, pitch ~90°). Разрешение кадров 512x512 px (подтверждено из JSON и реальных файлов). FOV 30° по вертикали (из camera trajectory JSON). Частота кадров 30 fps, 2.5 сек на траекторию. Кадров на траекторию 76 (75 кадров анимации + начальный). Формат JPEG.
+
Спутниковые виды (gallery / DB): Платформа Спутник (Google Earth / Google Maps tiles). Формат PNG (кропы), GeoTIFF (merge.tif). Разрешение кропов от 100x100 до 1000x1000 px (11 уникальных размеров, см. раздел 6). GSD варьируется по сценам (зависит от zoom level при рендеринге).
+
Раздел 6: Параметр нарезки. Стратегия Sliding window с фиксированным overlap. Overlap 50% (stride = crop_size / 2). Именование crop_X_Y.png — X по ширине (col), Y по высоте (row). Позиция в merge.tif: merge[Y*stride : Y*stride+crop_size, X*stride : X*stride+crop_size].
+
5.2. Параметры съёмки дронов: Высоты полёта 100 м, 125 м, 150 м (3 значения). Азимуты камеры (heading) 0°, 45°, 90°, 135°, 180°, 225°, 270°, 315° (8 направлений, шаг 45°). Число вариантов на сцену 24 (3 высоты x 8 азимутов) для Country; 8-24 для Terrain. FOV камеры 30° (вертикальный, подтверждено).
+
+
+
+
+
+
Информация
+
+
+ Текущие координаты:
+ Не установлены
+
+
+
+ Размер сетки:
+ 2x2
+
+
+
+ Всего точек:
+ 4
+
+
+
+ Статус:
+ Готов к работе
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mapview.html b/mapview.html
new file mode 100644
index 0000000..9550203
--- /dev/null
+++ b/mapview.html
@@ -0,0 +1,330 @@
+
+
+
+
+
+
+ Карта зоны
+
+
+
+
+
+ Карта зоны
+ Ожидание данных...
+ rx: 0
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..7f493c7
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,854 @@
+{
+ "name": "sandcastle",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "sandcastle",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "express": "^4.18.2",
+ "pngjs": "^7.0.0",
+ "utif": "^3.1.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/pngjs": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
+ "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.19.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
+ "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utif": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/utif/-/utif-3.1.0.tgz",
+ "integrity": "sha512-WEo4D/xOvFW53K5f5QTaTbbiORcm2/pCL9P6qmJnup+17eYfKaEhDeX9PeQkuyEoIxlbGklDuGl8xwuXYMrrXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^1.0.5"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..2ed6a7d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "sandcastle",
+ "version": "1.0.0",
+ "description": "SandCastle - Координаты и захват изображений",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js"
+ },
+ "keywords": [
+ "cesium",
+ "coordinates",
+ "capture"
+ ],
+ "author": "",
+ "license": "MIT",
+ "dependencies": {
+ "express": "^4.18.2",
+ "pngjs": "^7.0.0",
+ "utif": "^3.1.0"
+ }
+}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..c9b421c
--- /dev/null
+++ b/server.js
@@ -0,0 +1,503 @@
+const express = require("express");
+const fs = require("fs").promises;
+const path = require("path");
+const { PNG } = require("pngjs");
+const UTIF = require("utif");
+
+const app = express();
+const PORT = Math.max(1, Number.parseInt(process.env.PORT, 10) || 3000);
+
+app.use(express.json({ limit: "50mb" }));
+app.use(express.static(__dirname));
+
+const SETTINGS_FILE = path.join(__dirname, "settings.json");
+const OUTPUT_DIR = path.join(__dirname, "output");
+const METERS_PER_DEG_LAT = 111320;
+const DMS_VALUE_REGEX = /^\s*(\d+)\s*(?:°|В°)\s*(\d+)\s*'\s*([\d.]+)\s*"?\s*([NSEW])\s*$/i;
+const DMS_TOKEN_REGEX = /\d+\s*(?:°|В°)\s*\d+\s*'\s*[\d.]+\s*"?\s*[NSEW]/gi;
+
+function metersToLat(meters) {
+ return meters / METERS_PER_DEG_LAT;
+}
+
+function metersToLon(meters, latDeg) {
+ return meters / (METERS_PER_DEG_LAT * Math.cos((latDeg * Math.PI) / 180));
+}
+
+function dmsToDecimal(dms) {
+ const match = String(dms).trim().match(DMS_VALUE_REGEX);
+ if (!match) {
+ throw new Error(`Неверный формат координат: ${dms}`);
+ }
+
+ const degrees = Number.parseInt(match[1], 10);
+ const minutes = Number.parseInt(match[2], 10);
+ const seconds = Number.parseFloat(match[3]);
+ const direction = match[4].toUpperCase();
+
+ let decimal = degrees + minutes / 60 + seconds / 3600;
+ if (direction === "S" || direction === "W") {
+ decimal = -decimal;
+ }
+
+ return decimal;
+}
+
+function parseCoordinatePair(value) {
+ const tokens = String(value || "").match(DMS_TOKEN_REGEX) || [];
+ if (tokens.length !== 2) {
+ throw new Error(`Ожидалась пара DMS (широта и долгота): ${value}`);
+ }
+
+ const coords = tokens.map((token) => ({
+ value: dmsToDecimal(token),
+ direction: token.trim().slice(-1).toUpperCase()
+ }));
+
+ const lat = coords.find((c) => c.direction === "N" || c.direction === "S");
+ const lon = coords.find((c) => c.direction === "E" || c.direction === "W");
+
+ if (!lat || !lon) {
+ throw new Error(`В паре должны быть широта и долгота: ${value}`);
+ }
+
+ return { lat: lat.value, lon: lon.value };
+}
+
+function getBoundsFromSettings(settings) {
+ const text1 = String(settings.point1 || "").trim();
+ const text2 = String(settings.point2 || "").trim();
+
+ if (!text1 || !text2) {
+ throw new Error("Координаты не указаны");
+ }
+
+ const p1 = parseCoordinatePair(text1);
+ const p2 = parseCoordinatePair(text2);
+
+ return {
+ minLat: Math.min(p1.lat, p2.lat),
+ maxLat: Math.max(p1.lat, p2.lat),
+ minLon: Math.min(p1.lon, p2.lon),
+ maxLon: Math.max(p1.lon, p2.lon)
+ };
+}
+
+function decimalToDMS(decimal, isLatitude) {
+ const absolute = Math.abs(decimal);
+ const degrees = Math.floor(absolute);
+ const minutesFloat = (absolute - degrees) * 60;
+ let minutes = Math.floor(minutesFloat);
+ let seconds = Math.round((minutesFloat - minutes) * 60);
+
+ if (seconds === 60) {
+ seconds = 0;
+ minutes += 1;
+ }
+
+ const direction = isLatitude
+ ? decimal >= 0 ? "N" : "S"
+ : decimal >= 0 ? "E" : "W";
+
+ return `${degrees}°${minutes}'${seconds}"${direction}`;
+}
+
+function normalizeSettings(rawSettings) {
+ const source = rawSettings || {};
+ const settings = {
+ dataset: String(source.dataset || "Country").trim() || "Country",
+ country: String(source.country || "Custom").trim() || "Custom",
+ city: String(source.city || "Scene").trim() || "Scene",
+ region: String(source.region || "Region").trim() || "Region",
+ point1: String(source.point1 || "").trim(),
+ point2: String(source.point2 || "").trim(),
+ tile_m: Number(source.tile_m),
+ grid_step: Math.max(1, Number.parseInt(source.grid_step, 10) || 1),
+ height_100: source.height_100 !== undefined ? Boolean(source.height_100) : true,
+ height_125: source.height_125 !== undefined ? Boolean(source.height_125) : true,
+ height_150: source.height_150 !== undefined ? Boolean(source.height_150) : true,
+ height_100_value: Math.max(1, Number(source.height_100_value) || 100),
+ height_125_value: Math.max(1, Number(source.height_125_value) || 125),
+ height_150_value: Math.max(1, Number(source.height_150_value) || 150),
+ height_absolute: source.height_absolute !== undefined ? Boolean(source.height_absolute) : true,
+ db_crop_sizes: String(source.db_crop_sizes || "100,150,200,250,300,400,500,512,600,800,1000").trim(),
+ drone_size: Math.max(1, Number.parseInt(source.drone_size, 10) || 512),
+ query_fov_deg: Math.min(179, Math.max(1, Number(source.query_fov_deg) || 30)),
+ query_pitch_deg: -90,
+ preload: source.preload !== undefined ? Boolean(source.preload) : true,
+ skip_empty: Boolean(source.skip_empty)
+ };
+
+ const uniqueDbSizes = Array.from(new Set(
+ String(settings.db_crop_sizes || "")
+ .split(",")
+ .map((v) => Number.parseInt(v.trim(), 10))
+ .filter((v) => Number.isInteger(v) && v > 0)
+ ));
+
+ if (!Number.isFinite(settings.tile_m) || settings.tile_m <= 0) {
+ throw new Error("Tile (метры) должен быть больше 0");
+ }
+ if (!settings.height_100 && !settings.height_125 && !settings.height_150) {
+ throw new Error("Выберите хотя бы одну высоту (100/125/150)");
+ }
+ if (uniqueDbSizes.length !== 11) {
+ throw new Error("DB crop sizes: нужно ровно 11 уникальных размеров");
+ }
+
+ settings.db_crop_sizes = uniqueDbSizes.join(",");
+ getBoundsFromSettings(settings);
+ return settings;
+}
+
+function normalizeSceneSettings(rawSettings) {
+ const source = rawSettings || {};
+ return {
+ dataset: String(source.dataset || "Country").trim() || "Country",
+ country: String(source.country || "Custom").trim() || "Custom",
+ city: String(source.city || "Scene").trim() || "Scene",
+ region: String(source.region || "Region").trim() || "Region"
+ };
+}
+
+function computeGrid(bounds, tileMeters) {
+ const anchorLat = bounds.maxLat;
+ const latSpanM = (bounds.maxLat - bounds.minLat) * METERS_PER_DEG_LAT;
+ const lonSpanM = (bounds.maxLon - bounds.minLon) * METERS_PER_DEG_LAT * Math.cos((anchorLat * Math.PI) / 180);
+ const cols = Math.max(1, Math.floor(lonSpanM / tileMeters) + 1);
+ const rows = Math.max(1, Math.floor(latSpanM / tileMeters) + 1);
+ return { cols, rows };
+}
+
+function generateCoordinates(settings) {
+ const bounds = getBoundsFromSettings(settings);
+ const tileMeters = Math.max(1, Number(settings.tile_m) || 1);
+ const step = Math.max(1, settings.grid_step || 1);
+ const { cols, rows } = computeGrid(bounds, tileMeters);
+
+ const coordinates = [];
+ let index = 1;
+
+ for (let row = 0; row < rows; row += step) {
+ for (let col = 0; col < cols; col += step) {
+ const lat = bounds.maxLat - metersToLat(row * tileMeters);
+ const lon = bounds.minLon + metersToLon(col * tileMeters, lat);
+
+ coordinates.push({
+ index: index++,
+ row,
+ col,
+ lat,
+ lon,
+ latDMS: decimalToDMS(lat, true),
+ lonDMS: decimalToDMS(lon, false)
+ });
+ }
+ }
+
+ return { coordinates, bounds, gridRows: rows, gridCols: cols };
+}
+
+function dataUrlToBuffer(dataUrl) {
+ if (typeof dataUrl !== "string") {
+ throw new Error("Некорректные данные изображения");
+ }
+
+ const match = dataUrl.match(/^data:image\/(png|jpeg);base64,(.+)$/);
+ if (!match) {
+ throw new Error("Ожидался PNG или JPEG в формате data URL");
+ }
+
+ return Buffer.from(match[2], "base64");
+}
+
+function pngBufferToTiffBuffer(pngBuffer) {
+ const png = PNG.sync.read(pngBuffer);
+ return Buffer.from(UTIF.encodeImage(png.data, png.width, png.height));
+}
+
+function sanitizeNameSegment(name, fallback) {
+ const clean = String(name || "")
+ .trim()
+ .replace(/[\\/:*?"<>|]/g, "_")
+ .replace(/\s+/g, "_");
+ return clean || fallback;
+}
+
+function getScenePath(settings) {
+ return path.join(
+ OUTPUT_DIR,
+ sanitizeNameSegment(settings.dataset, "Country"),
+ sanitizeNameSegment(settings.country, "Custom"),
+ sanitizeNameSegment(settings.city, "Scene"),
+ sanitizeNameSegment(settings.region, "Region")
+ );
+}
+
+app.post("/api/save-settings", async (req, res) => {
+ try {
+ const settings = normalizeSettings(req.body);
+ await fs.writeFile(SETTINGS_FILE, JSON.stringify(settings, null, 2));
+ res.json({ success: true, message: "Настройки сохранены" });
+ } catch (error) {
+ console.error("Ошибка сохранения настроек:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/create-folders", async (req, res) => {
+ try {
+ const settings = normalizeSettings(req.body);
+ const { coordinates, bounds, gridRows, gridCols } = generateCoordinates(settings);
+ const scenePath = getScenePath(settings);
+ const regionName = sanitizeNameSegment(settings.region, "Region");
+
+ await fs.mkdir(path.join(scenePath, "DB", "img"), { recursive: true });
+ await fs.mkdir(path.join(scenePath, "query"), { recursive: true });
+
+ await fs.writeFile(path.join(scenePath, "DB", "db_postion.txt"), "");
+
+ const regionTxt =
+ `${bounds.maxLat} ${bounds.minLon}\n` +
+ `${bounds.minLat} ${bounds.maxLon}\n`;
+ await fs.writeFile(path.join(scenePath, `${regionName}.txt`), regionTxt);
+
+ const folders = [
+ {
+ name: regionName,
+ path: scenePath,
+ coordinates_count: coordinates.length
+ }
+ ];
+
+ res.json({
+ success: true,
+ folders,
+ gridRows,
+ gridCols,
+ bounds,
+ pointsCount: coordinates.length,
+ scenePath,
+ message: `Сцена создана, точек: ${coordinates.length}`
+ });
+ } catch (error) {
+ console.error("Ошибка создания сцены:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/save-db-crop", async (req, res) => {
+ try {
+ const settings = normalizeSceneSettings(req.body);
+ const col = Number.parseInt(req.body.col, 10);
+ const row = Number.parseInt(req.body.row, 10);
+ const lat = Number(req.body.lat);
+ const lon = Number(req.body.lon);
+ const resX = Number.parseInt(req.body.res_x, 10) || 0;
+ const resY = Number.parseInt(req.body.res_y, 10) || 0;
+
+ if (!Number.isInteger(col) || !Number.isInteger(row)) {
+ throw new Error("Некорректные col/row");
+ }
+ if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
+ throw new Error("Некорректные координаты crop");
+ }
+
+ const pngBuffer = dataUrlToBuffer(req.body.png);
+ const scenePath = getScenePath(settings);
+ const imgDir = path.join(scenePath, "DB", "img");
+ await fs.mkdir(imgDir, { recursive: true });
+
+ const sizeTag = Number.parseInt(req.body.size_tag, 10);
+ const suffix = Number.isInteger(sizeTag) && sizeTag > 0 ? `_${sizeTag}` : "";
+ const filename = `crop_${col}_${row}${suffix}.png`;
+ await fs.writeFile(path.join(imgDir, filename), pngBuffer);
+
+ const line = `${filename} ${lon} ${lat} ${resX} ${resY}\n`;
+ await fs.appendFile(path.join(scenePath, "DB", "db_postion.txt"), line);
+
+ res.json({ success: true, filename });
+ } catch (error) {
+ console.error("Ошибка сохранения DB crop:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/save-merge-tif", async (req, res) => {
+ try {
+ const settings = normalizeSceneSettings(req.body);
+ const pngBuffer = dataUrlToBuffer(req.body.png);
+ const tiffBuffer = pngBufferToTiffBuffer(pngBuffer);
+ const bounds = getBoundsFromSettings(req.body);
+ const scenePath = getScenePath(settings);
+ const dbDir = path.join(scenePath, "DB");
+ await fs.mkdir(dbDir, { recursive: true });
+ await fs.writeFile(path.join(dbDir, "merge.tif"), tiffBuffer);
+ await fs.writeFile(path.join(dbDir, "geo.tif"), tiffBuffer);
+ await fs.writeFile(
+ path.join(dbDir, "geo_bounds.json"),
+ JSON.stringify(
+ {
+ crs: "EPSG:4326",
+ bounds
+ },
+ null,
+ 2
+ )
+ );
+ res.json({ success: true, files: ["merge.tif", "geo.tif", "geo_bounds.json"] });
+ } catch (error) {
+ console.error("Ошибка сохранения merge.tif:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/save-drone-frame", async (req, res) => {
+ try {
+ const settings = normalizeSceneSettings(req.body);
+ const heightValue = Number(req.body.height);
+ const rotValue = Number.isFinite(Number(req.body.rot)) ? Number(req.body.rot) : 0;
+ const frameIdx = Number.parseInt(req.body.frameIdx, 10);
+
+ if (!Number.isFinite(heightValue) || heightValue <= 0) {
+ throw new Error("Некорректная высота");
+ }
+ if (!Number.isInteger(frameIdx) || frameIdx < 0) {
+ throw new Error("Некорректный frameIdx");
+ }
+
+ const jpegBuffer = dataUrlToBuffer(req.body.jpeg);
+ const scenePath = getScenePath(settings);
+ const variantName = `height${heightValue}_rot${rotValue}`;
+ const footageDir = path.join(scenePath, "query", variantName, "footage");
+ await fs.mkdir(footageDir, { recursive: true });
+
+ const frameStr = String(frameIdx).padStart(2, "0");
+ const filename = `${variantName}_${frameStr}.jpeg`;
+ await fs.writeFile(path.join(footageDir, filename), jpegBuffer);
+
+ res.json({ success: true, filename });
+ } catch (error) {
+ console.error("Ошибка сохранения drone frame:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/save-trajectory", async (req, res) => {
+ try {
+ const settings = normalizeSceneSettings(req.body);
+ const heightValue = Number(req.body.height);
+ const rotValue = Number.isFinite(Number(req.body.rot)) ? Number(req.body.rot) : 0;
+ const trajectory = req.body.trajectory;
+
+ if (!Number.isFinite(heightValue) || heightValue <= 0) {
+ throw new Error("Некорректная высота");
+ }
+ if (!trajectory || !Array.isArray(trajectory.cameraFrames)) {
+ throw new Error("Некорректная траектория");
+ }
+
+ const scenePath = getScenePath(settings);
+ const variantName = `height${heightValue}_rot${rotValue}`;
+ const variantDir = path.join(scenePath, "query", variantName);
+ await fs.mkdir(variantDir, { recursive: true });
+
+ const filePath = path.join(variantDir, `${variantName}.json`);
+ await fs.writeFile(filePath, JSON.stringify(trajectory, null, 2));
+
+ res.json({ success: true, filename: `${variantName}.json` });
+ } catch (error) {
+ console.error("Ошибка сохранения trajectory:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/save-annotations", async (req, res) => {
+ try {
+ const settings = normalizeSceneSettings(req.body);
+ const positive = req.body.positive || {};
+ const semiPositive = req.body.semi_positive || {};
+
+ if (typeof positive !== "object" || Array.isArray(positive) ||
+ typeof semiPositive !== "object" || Array.isArray(semiPositive)) {
+ throw new Error("Некорректные аннотации");
+ }
+
+ const scenePath = getScenePath(settings);
+ await fs.mkdir(scenePath, { recursive: true });
+ await fs.writeFile(path.join(scenePath, "positive.json"), JSON.stringify(positive, null, 2));
+ await fs.writeFile(path.join(scenePath, "semi_positive.json"), JSON.stringify(semiPositive, null, 2));
+
+ res.json({ success: true, files: ["positive.json", "semi_positive.json"] });
+ } catch (error) {
+ console.error("Ошибка сохранения аннотаций:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.post("/api/start-capture", async (req, res) => {
+ try {
+ const settings = normalizeSettings(req.body);
+ await fs.writeFile(SETTINGS_FILE, JSON.stringify(settings, null, 2));
+
+ res.json({
+ success: true,
+ url: `http://localhost:${PORT}/capture.html`,
+ message: "Захват запущен"
+ });
+ } catch (error) {
+ console.error("Ошибка запуска захвата:", error);
+ res.status(500).json({ success: false, error: error.message });
+ }
+});
+
+app.get("/api/get-settings", async (req, res) => {
+ try {
+ const data = await fs.readFile(SETTINGS_FILE, "utf8");
+ const settings = JSON.parse(data);
+ settings.query_pitch_deg = -90;
+ res.json({ success: true, settings });
+ } catch (error) {
+ res.json({
+ success: true,
+ settings: {
+ dataset: "Country",
+ country: "German",
+ city: "Hannover",
+ region: "Custom",
+ point1: `52°25'10.28"N 9°39'10.04"E`,
+ point2: `52°25'07.34"N 9°39'23.65"E`,
+ tile_m: 49,
+ grid_step: 1,
+ height_100: true,
+ height_125: true,
+ height_150: true,
+ height_100_value: 100,
+ height_125_value: 125,
+ height_150_value: 150,
+ height_absolute: true,
+ db_crop_sizes: "100,150,200,250,300,400,500,512,600,800,1000",
+ drone_size: 512,
+ query_fov_deg: 30,
+ query_pitch_deg: -90,
+ preload: true,
+ skip_empty: false
+ }
+ });
+ }
+});
+
+app.listen(PORT, () => {
+ console.log(`
+╔════════════════════════════════════════════════════════════╗
+║ ║
+║ 🗺️ SandCastle Server запущен! ║
+║ ║
+║ 📌 URL: http://localhost:${PORT} ║
+║ 📂 Папка вывода: ${OUTPUT_DIR} ║
+║ ║
+║ Откройте браузер и перейдите по адресу выше ║
+║ ║
+╚════════════════════════════════════════════════════════════╝
+ `);
+});
diff --git a/settings.json b/settings.json
new file mode 100644
index 0000000..219129d
--- /dev/null
+++ b/settings.json
@@ -0,0 +1,23 @@
+{
+ "dataset": "Country",
+ "country": "German",
+ "city": "Milan",
+ "region": "Milan",
+ "point1": "45°28'24.38\"N 9°10'10.45\"E",
+ "point2": "45°27'16.41\"N 9°12'24.26\"E",
+ "tile_m": 49,
+ "grid_step": 1,
+ "height_100": false,
+ "height_125": true,
+ "height_150": true,
+ "height_100_value": 100,
+ "height_125_value": 100,
+ "height_150_value": 125,
+ "height_absolute": false,
+ "db_crop_sizes": "100,150,200,250,300,400,500,512,600,800,1000",
+ "drone_size": 512,
+ "query_fov_deg": 30,
+ "query_pitch_deg": -90,
+ "preload": false,
+ "skip_empty": false
+}
\ No newline at end of file
diff --git a/start.bat b/start.bat
new file mode 100644
index 0000000..3bd8c8c
--- /dev/null
+++ b/start.bat
@@ -0,0 +1,57 @@
+@echo off
+chcp 65001 >nul
+cls
+
+echo.
+echo ╔════════════════════════════════════════════════════════════╗
+echo ║ ║
+echo ║ 🗺️ SandCastle - Запуск программы ║
+echo ║ ║
+echo ╚════════════════════════════════════════════════════════════╝
+echo.
+
+REM Проверка наличия Node.js
+where node >nul 2>nul
+if %errorlevel% neq 0 (
+ echo ❌ Node.js не установлен!
+ echo.
+ echo Пожалуйста, установите Node.js с https://nodejs.org/
+ echo.
+ pause
+ exit /b 1
+)
+
+echo ✅ Node.js найден
+echo.
+
+REM Проверка наличия node_modules
+if not exist "node_modules" (
+ echo 📦 Установка зависимостей...
+ echo.
+ call npm install
+ echo.
+ if %errorlevel% neq 0 (
+ echo ❌ Ошибка установки зависимостей
+ pause
+ exit /b 1
+ )
+ echo ✅ Зависимости установлены
+ echo.
+)
+
+REM Запуск сервера
+echo 🚀 Запуск сервера...
+echo.
+
+set "PORT=%~1"
+if "%PORT%"=="" set "PORT=3000"
+
+echo 🌐 Порт: %PORT%
+echo.
+
+start "" "http://localhost:%PORT%"
+
+set "PORT=%PORT%"
+node server.js
+
+pause
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..71536b0
--- /dev/null
+++ b/style.css
@@ -0,0 +1,530 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
+
+:root {
+ --primary: #6366f1;
+ --primary-dark: #4f46e5;
+ --secondary: #8b5cf6;
+ --success: #10b981;
+ --success-dark: #059669;
+ --danger: #ef4444;
+ --warning: #f59e0b;
+ --bg-dark: #0f172a;
+ --bg-card: #1e293b;
+ --bg-card-hover: #334155;
+ --text-primary: #f1f5f9;
+ --text-secondary: #94a3b8;
+ --border: #334155;
+ --shadow: rgba(0, 0, 0, 0.3);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
+ color: var(--text-primary);
+ min-height: 100vh;
+ padding: 2rem;
+ line-height: 1.6;
+}
+
+.container {
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+/* Header */
+header {
+ text-align: center;
+ margin-bottom: 3rem;
+ animation: fadeInDown 0.6s ease-out;
+}
+
+header h1 {
+ font-size: 3rem;
+ font-weight: 700;
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ margin-bottom: 0.5rem;
+}
+
+.subtitle {
+ color: var(--text-secondary);
+ font-size: 1.1rem;
+ font-weight: 400;
+}
+
+/* Main Content Grid */
+.main-content {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+ margin-bottom: 2rem;
+}
+
+@media (max-width: 1024px) {
+ .main-content {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Cards */
+.card {
+ background: var(--bg-card);
+ border-radius: 1rem;
+ padding: 2rem;
+ box-shadow: 0 10px 30px var(--shadow);
+ border: 1px solid var(--border);
+ transition: all 0.3s ease;
+ animation: fadeInUp 0.6s ease-out;
+}
+
+.card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 15px 40px var(--shadow);
+}
+
+.card h2 {
+ font-size: 1.5rem;
+ margin-bottom: 1.5rem;
+ color: var(--text-primary);
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+/* Form Elements */
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ color: var(--text-primary);
+ font-size: 0.95rem;
+}
+
+input[type="text"],
+input[type="number"] {
+ width: 100%;
+ padding: 0.875rem 1rem;
+ background: var(--bg-dark);
+ border: 2px solid var(--border);
+ border-radius: 0.5rem;
+ color: var(--text-primary);
+ font-size: 1rem;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.3s ease;
+}
+
+input[type="text"]:focus,
+input[type="number"]:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
+}
+
+.hint {
+ display: block;
+ margin-top: 0.5rem;
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+ font-style: italic;
+}
+
+.section-block {
+ margin: 1.25rem 0;
+ padding: 1rem;
+ border: 1px solid var(--border);
+ border-radius: 0.75rem;
+ background: rgba(15, 23, 42, 0.35);
+}
+
+.section-block h3 {
+ margin-bottom: 0.9rem;
+ font-size: 1rem;
+ color: #c7d2fe;
+}
+
+.height-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 0.75rem;
+}
+
+.height-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0;
+}
+
+.height-item input[type="checkbox"] {
+ width: auto;
+}
+
+.height-item input[type="number"] {
+ min-width: 70px;
+ padding: 0.45rem 0.55rem;
+ font-size: 0.9rem;
+}
+
+.cheatsheet-note {
+ margin-top: 1.5rem;
+ font-size: 0.74rem;
+ color: var(--text-secondary);
+ line-height: 1.45;
+}
+
+.cheatsheet-note p {
+ margin-top: 0.35rem;
+}
+
+/* Buttons */
+.button-group {
+ display: flex;
+ gap: 1rem;
+ flex-wrap: wrap;
+ margin-top: 2rem;
+}
+
+.btn {
+ flex: 1;
+ min-width: 150px;
+ padding: 1rem 1.5rem;
+ border: none;
+ border-radius: 0.75rem;
+ font-size: 1rem;
+ font-weight: 600;
+ font-family: 'Inter', sans-serif;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ position: relative;
+ overflow: hidden;
+}
+
+.btn-small {
+ margin-top: 0.7rem;
+ width: auto;
+ min-width: unset;
+ padding: 0.55rem 0.8rem;
+ font-size: 0.82rem;
+ border-radius: 0.55rem;
+}
+
+.btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.2);
+ transform: translate(-50%, -50%);
+ transition: width 0.6s, height 0.6s;
+}
+
+.btn:hover::before {
+ width: 300px;
+ height: 300px;
+}
+
+.btn-icon {
+ font-size: 1.2rem;
+ position: relative;
+ z-index: 1;
+}
+
+.btn span:not(.btn-icon) {
+ position: relative;
+ z-index: 1;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
+ color: white;
+}
+
+.btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 25px rgba(99, 102, 241, 0.4);
+}
+
+.btn-secondary {
+ background: linear-gradient(135deg, var(--secondary) 0%, #7c3aed 100%);
+ color: white;
+}
+
+.btn-secondary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 25px rgba(139, 92, 246, 0.4);
+}
+
+.btn-success {
+ background: linear-gradient(135deg, var(--success) 0%, var(--success-dark) 100%);
+ color: white;
+}
+
+.btn-success:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 25px rgba(16, 185, 129, 0.4);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+/* Info Panel */
+.info-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem;
+ background: var(--bg-dark);
+ border-radius: 0.5rem;
+ margin-bottom: 0.75rem;
+ transition: all 0.3s ease;
+}
+
+.info-item:hover {
+ background: var(--bg-card-hover);
+}
+
+.info-label {
+ font-weight: 500;
+ color: var(--text-secondary);
+}
+
+.info-value {
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.status {
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+ background: var(--success);
+ color: white;
+ font-size: 0.9rem;
+}
+
+.status.working {
+ background: var(--warning);
+ animation: pulse 2s infinite;
+}
+
+.status.error {
+ background: var(--danger);
+}
+
+/* Log Container */
+.log-container {
+ margin-top: 2rem;
+}
+
+.log-container h3 {
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ color: var(--text-primary);
+}
+
+.log-output {
+ background: var(--bg-dark);
+ border-radius: 0.5rem;
+ padding: 1rem;
+ max-height: 300px;
+ overflow-y: auto;
+ font-family: 'Courier New', monospace;
+ font-size: 0.85rem;
+ line-height: 1.8;
+}
+
+.log-output::-webkit-scrollbar {
+ width: 8px;
+}
+
+.log-output::-webkit-scrollbar-track {
+ background: var(--bg-card);
+ border-radius: 4px;
+}
+
+.log-output::-webkit-scrollbar-thumb {
+ background: var(--primary);
+ border-radius: 4px;
+}
+
+.log-entry {
+ margin-bottom: 0.5rem;
+ padding: 0.5rem;
+ border-left: 3px solid var(--primary);
+ padding-left: 0.75rem;
+ animation: slideInLeft 0.3s ease-out;
+}
+
+.log-entry.success {
+ border-left-color: var(--success);
+ color: var(--success);
+}
+
+.log-entry.error {
+ border-left-color: var(--danger);
+ color: var(--danger);
+}
+
+.log-entry.info {
+ border-left-color: var(--primary);
+ color: var(--text-secondary);
+}
+
+/* Folders List */
+.folders-card {
+ animation: fadeInUp 0.8s ease-out;
+}
+
+.folders-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 1rem;
+}
+
+.folder-item {
+ background: var(--bg-dark);
+ padding: 1rem;
+ border-radius: 0.5rem;
+ border: 2px solid var(--border);
+ transition: all 0.3s ease;
+ cursor: pointer;
+ animation: scaleIn 0.3s ease-out;
+}
+
+.folder-item:hover {
+ border-color: var(--primary);
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(99, 102, 241, 0.2);
+}
+
+.folder-item .folder-icon {
+ font-size: 2rem;
+ margin-bottom: 0.5rem;
+}
+
+.folder-item .folder-name {
+ font-weight: 500;
+ color: var(--text-primary);
+ word-break: break-all;
+}
+
+.empty-state {
+ text-align: center;
+ color: var(--text-secondary);
+ padding: 2rem;
+ font-style: italic;
+}
+
+/* Animations */
+@keyframes fadeInDown {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes fadeInUp {
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes slideInLeft {
+ from {
+ opacity: 0;
+ transform: translateX(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.7;
+ }
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ body {
+ padding: 1rem;
+ }
+
+ header h1 {
+ font-size: 2rem;
+ }
+
+ .card {
+ padding: 1.5rem;
+ }
+
+ .form-row {
+ grid-template-columns: 1fr;
+ }
+
+ .button-group {
+ flex-direction: column;
+ }
+
+ .btn {
+ width: 100%;
+ }
+
+ .folders-list {
+ grid-template-columns: 1fr;
+ }
+
+ .height-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/ui.js b/ui.js
new file mode 100644
index 0000000..c0a04b9
--- /dev/null
+++ b/ui.js
@@ -0,0 +1,373 @@
+// UI Controller for SandCastle
+
+class SandCastleUI {
+ constructor() {
+ this.settings = {
+ dataset: 'Country',
+ country: 'German',
+ city: 'Hannover',
+ region: 'Custom',
+ point1: '52°25\'10.28"N 9°39\'10.04"E',
+ point2: '52°25\'07.34"N 9°39\'23.65"E',
+ tile_m: 49,
+ grid_step: 1,
+ height_100: true,
+ height_125: true,
+ height_150: true,
+ height_100_value: 100,
+ height_125_value: 125,
+ height_150_value: 150,
+ height_absolute: true,
+ db_crop_sizes: "100,150,200,250,300,400,500,512,600,800,1000",
+ drone_size: 512,
+ query_fov_deg: 30,
+ query_pitch_deg: -90,
+ preload: true,
+ skip_empty: false
+ };
+
+ this.METERS_PER_DEG_LAT = 111320;
+ this.DEFAULT_DB_CROP_SIZES = '100,150,200,250,300,400,500,512,600,800,1000';
+ this.DMS_VALUE_REGEX = /^\s*(\d+)\s*(?:°|В°)\s*(\d+)\s*'\s*([\d.]+)\s*"?\s*([NSEW])\s*$/i;
+ this.DMS_TOKEN_REGEX = /\d+\s*(?:°|В°)\s*\d+\s*'\s*[\d.]+\s*"?\s*[NSEW]/gi;
+
+ this.folders = [];
+ this.initializeElements();
+ this.attachEventListeners();
+ this.loadSettingsFromServer();
+ this.updateInfo();
+ }
+
+ initializeElements() {
+ this.datasetInput = document.getElementById('dataset');
+ this.countryInput = document.getElementById('country');
+ this.cityInput = document.getElementById('city');
+ this.regionInput = document.getElementById('region');
+ this.point1Input = document.getElementById('point1');
+ this.point2Input = document.getElementById('point2');
+ this.tileMInput = document.getElementById('tile_m');
+ this.gridStepInput = document.getElementById('grid_step');
+ this.dbCropSizesInput = document.getElementById('db_crop_sizes');
+ this.droneSizeInput = document.getElementById('drone_size');
+ this.queryFovInput = document.getElementById('query_fov_deg');
+ this.queryPitchInput = document.getElementById('query_pitch_deg');
+ this.height100Input = document.getElementById('height_100');
+ this.height125Input = document.getElementById('height_125');
+ this.height150Input = document.getElementById('height_150');
+ this.height100ValueInput = document.getElementById('height_100_value');
+ this.height125ValueInput = document.getElementById('height_125_value');
+ this.height150ValueInput = document.getElementById('height_150_value');
+ this.heightAbsoluteInput = document.getElementById('height_absolute');
+ this.preloadInput = document.getElementById('preload');
+ this.skipEmptyInput = document.getElementById('skip_empty');
+ this.fillDbSizesBtn = document.getElementById('fillDbSizesBtn');
+
+ this.saveBtn = document.getElementById('saveBtn');
+ this.createFoldersBtn = document.getElementById('createFoldersBtn');
+ this.startCaptureBtn = document.getElementById('startCaptureBtn');
+
+ this.currentCoords = document.getElementById('currentCoords');
+ this.currentGrid = document.getElementById('currentGrid');
+ this.totalPoints = document.getElementById('totalPoints');
+ this.status = document.getElementById('status');
+ this.logOutput = document.getElementById('logOutput');
+ this.foldersList = document.getElementById('foldersList');
+ }
+
+ attachEventListeners() {
+ this.saveBtn.addEventListener('click', () => this.saveSettings());
+ this.createFoldersBtn.addEventListener('click', () => this.createFolders());
+ this.startCaptureBtn.addEventListener('click', () => this.startCapture());
+ this.fillDbSizesBtn.addEventListener('click', () => {
+ this.dbCropSizesInput.value = this.DEFAULT_DB_CROP_SIZES;
+ this.updateInfo();
+ this.log('Подставлены стандартные 11 размеров DB', 'info');
+ });
+
+ [
+ this.point1Input,
+ this.point2Input,
+ this.tileMInput,
+ this.gridStepInput,
+ this.height100Input,
+ this.height125Input,
+ this.height150Input,
+ this.height100ValueInput,
+ this.height125ValueInput,
+ this.height150ValueInput,
+ this.dbCropSizesInput,
+ this.queryFovInput,
+ this.queryPitchInput
+ ].forEach((input) => {
+ const event = input.type === 'checkbox' ? 'change' : 'input';
+ input.addEventListener(event, () => this.updateInfo());
+ });
+ }
+
+ log(message, type = 'info') {
+ const entry = document.createElement('div');
+ entry.className = `log-entry ${type}`;
+ const timestamp = new Date().toLocaleTimeString('ru-RU');
+ entry.textContent = `[${timestamp}] ${message}`;
+ this.logOutput.appendChild(entry);
+ this.logOutput.scrollTop = this.logOutput.scrollHeight;
+ }
+
+ updateStatus(text, className = '') {
+ this.status.textContent = text;
+ this.status.className = 'info-value status ' + className;
+ }
+
+ dmsToDecimal(dms) {
+ const match = String(dms).trim().match(this.DMS_VALUE_REGEX);
+ if (!match) {
+ throw new Error(`Неверный формат DMS: ${dms}`);
+ }
+
+ const degrees = Number.parseInt(match[1], 10);
+ const minutes = Number.parseInt(match[2], 10);
+ const seconds = Number.parseFloat(match[3]);
+ const direction = match[4].toUpperCase();
+
+ let decimal = degrees + minutes / 60 + seconds / 3600;
+ if (direction === 'S' || direction === 'W') {
+ decimal = -decimal;
+ }
+
+ return decimal;
+ }
+
+ parseCoordinatePair(value) {
+ const tokens = String(value || '').match(this.DMS_TOKEN_REGEX) || [];
+ if (tokens.length !== 2) {
+ throw new Error('Ожидалась пара DMS');
+ }
+ const coords = tokens.map((t) => ({
+ value: this.dmsToDecimal(t),
+ direction: t.trim().slice(-1).toUpperCase()
+ }));
+ const lat = coords.find((c) => c.direction === 'N' || c.direction === 'S');
+ const lon = coords.find((c) => c.direction === 'E' || c.direction === 'W');
+ if (!lat || !lon) {
+ throw new Error('Нужны широта и долгота');
+ }
+ return { lat: lat.value, lon: lon.value };
+ }
+
+ updateInfo() {
+ const step = Math.max(1, Number.parseInt(this.gridStepInput.value, 10) || 1);
+ const tileMeters = Math.max(1, Number(this.tileMInput.value) || 1);
+ const heightCount =
+ (this.height100Input.checked ? 1 : 0) +
+ (this.height125Input.checked ? 1 : 0) +
+ (this.height150Input.checked ? 1 : 0);
+
+ try {
+ const p1 = this.parseCoordinatePair(this.point1Input.value);
+ const p2 = this.parseCoordinatePair(this.point2Input.value);
+ const minLat = Math.min(p1.lat, p2.lat);
+ const maxLat = Math.max(p1.lat, p2.lat);
+ const minLon = Math.min(p1.lon, p2.lon);
+ const maxLon = Math.max(p1.lon, p2.lon);
+ const latSpanM = (maxLat - minLat) * this.METERS_PER_DEG_LAT;
+ const lonSpanM = (maxLon - minLon) * this.METERS_PER_DEG_LAT * Math.cos((maxLat * Math.PI) / 180);
+ const cols = Math.max(1, Math.floor(lonSpanM / tileMeters) + 1);
+ const rows = Math.max(1, Math.floor(latSpanM / tileMeters) + 1);
+ const effectiveCols = Math.ceil(cols / step);
+ const effectiveRows = Math.ceil(rows / step);
+ const tiles = effectiveCols * effectiveRows;
+
+ this.currentGrid.textContent = `${effectiveCols}x${effectiveRows}`;
+ this.totalPoints.textContent = heightCount > 0 ? `${tiles} × ${heightCount}в = ${tiles * heightCount}` : `${tiles} (нет высот)`;
+ this.currentCoords.textContent = `${Math.round(lonSpanM)}×${Math.round(latSpanM)}м`;
+ } catch (error) {
+ this.currentGrid.textContent = '—';
+ this.totalPoints.textContent = '—';
+ }
+ }
+
+ collectSettings() {
+ const parsedDbSizes = this.parseDbCropSizes(this.dbCropSizesInput.value);
+ if (parsedDbSizes.length !== 11) {
+ throw new Error('DB crop sizes: укажите ровно 11 уникальных размеров');
+ }
+
+ this.settings = {
+ dataset: this.datasetInput.value.trim() || 'Country',
+ country: this.countryInput.value.trim() || 'Custom',
+ city: this.cityInput.value.trim() || 'Scene',
+ region: this.regionInput.value.trim() || 'Region',
+ point1: this.point1Input.value.trim(),
+ point2: this.point2Input.value.trim(),
+ tile_m: Math.max(1, Number(this.tileMInput.value) || 1),
+ grid_step: Math.max(1, Number.parseInt(this.gridStepInput.value, 10) || 1),
+ height_100: this.height100Input.checked,
+ height_125: this.height125Input.checked,
+ height_150: this.height150Input.checked,
+ height_100_value: Math.max(1, Number(this.height100ValueInput.value) || 100),
+ height_125_value: Math.max(1, Number(this.height125ValueInput.value) || 125),
+ height_150_value: Math.max(1, Number(this.height150ValueInput.value) || 150),
+ height_absolute: this.heightAbsoluteInput.checked,
+ db_crop_sizes: parsedDbSizes.join(','),
+ drone_size: Math.max(1, Number.parseInt(this.droneSizeInput.value, 10) || 512),
+ query_fov_deg: Math.max(1, Math.min(179, Number(this.queryFovInput.value) || 30)),
+ query_pitch_deg: -90,
+ preload: this.preloadInput.checked,
+ skip_empty: this.skipEmptyInput.checked
+ };
+ return this.settings;
+ }
+
+ parseDbCropSizes(raw) {
+ return Array.from(new Set(
+ String(raw || '')
+ .split(',')
+ .map((v) => Number.parseInt(v.trim(), 10))
+ .filter((v) => Number.isInteger(v) && v > 0)
+ ));
+ }
+
+ async loadSettingsFromServer() {
+ try {
+ const response = await fetch('/api/get-settings');
+ const result = await response.json();
+ if (!result.success || !result.settings) {
+ return;
+ }
+
+ const settings = result.settings;
+ this.datasetInput.value = settings.dataset ?? this.datasetInput.value;
+ this.countryInput.value = settings.country ?? this.countryInput.value;
+ this.cityInput.value = settings.city ?? this.cityInput.value;
+ this.regionInput.value = settings.region ?? this.regionInput.value;
+ this.point1Input.value = settings.point1 ?? this.point1Input.value;
+ this.point2Input.value = settings.point2 ?? this.point2Input.value;
+ this.tileMInput.value = settings.tile_m ?? this.tileMInput.value;
+ this.gridStepInput.value = settings.grid_step ?? this.gridStepInput.value;
+ this.dbCropSizesInput.value = settings.db_crop_sizes ?? this.dbCropSizesInput.value;
+ this.droneSizeInput.value = settings.drone_size ?? this.droneSizeInput.value;
+ this.queryFovInput.value = settings.query_fov_deg ?? this.queryFovInput.value;
+ this.queryPitchInput.value = -90;
+ this.height100Input.checked = settings.height_100 !== undefined ? !!settings.height_100 : true;
+ this.height125Input.checked = settings.height_125 !== undefined ? !!settings.height_125 : true;
+ this.height150Input.checked = settings.height_150 !== undefined ? !!settings.height_150 : true;
+ this.height100ValueInput.value = settings.height_100_value ?? this.height100ValueInput.value;
+ this.height125ValueInput.value = settings.height_125_value ?? this.height125ValueInput.value;
+ this.height150ValueInput.value = settings.height_150_value ?? this.height150ValueInput.value;
+ this.heightAbsoluteInput.checked = settings.height_absolute !== undefined ? !!settings.height_absolute : true;
+ this.preloadInput.checked = settings.preload !== undefined ? !!settings.preload : true;
+ this.skipEmptyInput.checked = !!settings.skip_empty;
+
+ this.collectSettings();
+ this.updateInfo();
+ this.log('Настройки загружены', 'info');
+ } catch (error) {
+ this.log('Не удалось загрузить сохраненные настройки', 'error');
+ }
+ }
+
+ async saveSettings() {
+ try {
+ this.collectSettings();
+ this.updateInfo();
+ const response = await fetch('/api/save-settings', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(this.settings)
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ this.log('Настройки сохранены', 'success');
+ this.updateStatus('Настройки сохранены', '');
+ } else {
+ throw new Error(result.error);
+ }
+ } catch (error) {
+ this.log(`Ошибка сохранения: ${error.message}`, 'error');
+ this.updateStatus('Ошибка', 'error');
+ }
+ }
+
+ async createFolders() {
+ try {
+ this.collectSettings();
+ this.updateStatus('Создание папок...', 'working');
+ this.log('Создание папок...', 'info');
+ const response = await fetch('/api/create-folders', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(this.settings)
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ this.folders = result.folders;
+ this.renderFolders();
+ if (result.gridRows && result.gridCols) {
+ this.log(`Сетка: ${result.gridCols}x${result.gridRows} (cols×rows)`, 'info');
+ }
+ this.log(`Создано папок: ${result.folders.length}`, 'success');
+ this.updateStatus('Папки созданы', '');
+ } else {
+ throw new Error(result.error);
+ }
+ } catch (error) {
+ this.log(`Ошибка создания папок: ${error.message}`, 'error');
+ this.updateStatus('Ошибка', 'error');
+ }
+ }
+
+ renderFolders() {
+ if (this.folders.length === 0) {
+ this.foldersList.innerHTML = 'Папки еще не созданы
';
+ return;
+ }
+
+ this.foldersList.innerHTML = this.folders.map(folder => `
+
+ `).join('');
+ }
+
+ async startCapture() {
+ try {
+ this.collectSettings();
+ this.updateStatus('Захват изображений...', 'working');
+ this.log('Запуск захвата...', 'info');
+ const response = await fetch('/api/start-capture', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(this.settings)
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ this.log(`Захват запущен. URL: ${result.url}`, 'success');
+ this.updateStatus('Захват запущен', '');
+ window.open(result.url, '_blank', 'width=900,height=900');
+ } else {
+ throw new Error(result.error);
+ }
+ } catch (error) {
+ this.log(`Ошибка запуска захвата: ${error.message}`, 'error');
+ this.updateStatus('Ошибка', 'error');
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ window.sandCastleUI = new SandCastleUI();
+ console.log('SandCastle UI initialized');
+});