class CesiumMapEditor { constructor() { this.viewer = null; this.currentMode = 'select'; // select, marker, polygon this.tempEntities = []; this.selectedEntity = null; this.drawingHandler = null; this.editPoints = []; this.currentPolygonPoints = []; this.currentPolylinePoints = []; this.isDrawing = false; const fileInput = document.getElementById('fileInput'); if (fileInput) { fileInput.addEventListener('change', (e) => this.handleFileSelect(e)); } this.initCesium(); this.initUI(); this.initEventListeners(); } initCesium() { // Инициализация Cesium Viewer :cite[1] Cesium.Ion.defaultAccessToken = undefined; const osmProvider = new Cesium.OpenStreetMapImageryProvider({ url: 'https://tile.openstreetmap.org/' }); const esriImagery = new Cesium.ArcGisMapServerImageryProvider({ url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' }); this.viewer = new Cesium.Viewer('cesiumContainer', { terrainProvider: Cesium.createWorldTerrain, imageryProvider: esriImagery, baseLayerPicker: false, homeButton: true, sceneModePicker: true, navigationHelpButton: false, animation: false, timeline: false, fullscreenButton: false, geocoder: false }); // this.viewer.imageryLayers.addImageryProvider(esriImagery); // this.viewer.scene.transitioner.enableTransitions = false; // Начальная позиция камеры :cite[1] // this.viewer.camera.setView({ // destination: Cesium.Cartesian3.fromDegrees(38.5238, 60.4547, 10000), // orientation: { // heading: 0.0, // pitch: -0.5, // roll: 0.0 // } // }); this.viewer.imageryLayers.removeAll(); this.viewer.imageryLayers.addImageryProvider(osmProvider); } initUI() { this.updateModeStatus(); } initEventListeners() { // Обработчики кнопок режимов document.getElementById('selectMode').addEventListener('click', () => this.setMode('select')); document.getElementById('markerMode').addEventListener('click', () => this.setMode('marker')); document.getElementById('polygonMode').addEventListener('click', () => this.setMode('polygon')); document.getElementById('polylineMode').addEventListener('click', () => this.setMode('polyline')); // Кнопки действий document.getElementById('deleteSelected').addEventListener('click', () => this.deleteSelected()); document.getElementById('clearAll').addEventListener('click', () => this.clearAll()); // Модальное окно document.getElementById('confirmDescription').addEventListener('click', () => this.confirmDescription()); document.getElementById('cancelDescription').addEventListener('click', () => this.cancelDescription()); // Обработка клавиатуры document.addEventListener('keydown', (e) => this.handleKeyDown(e)); // Обработка кликов по карте this.setupMapClickHandler(); this.setupMouseMoveHandler(); document.getElementById('importBtn').addEventListener('click', () => this.triggerFileImport()); document.getElementById('exportBtn').addEventListener('click', () => this.showExportModal()); document.getElementById('exportGeoJson').addEventListener('click', () => this.exportAsGeoJson()); document.getElementById('exportKml').addEventListener('click', () => this.exportAsKml()); document.getElementById('cancelExport').addEventListener('click', () => this.hideExportModal()); } setupMapClickHandler() { this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); // Обработка одинарного клика this.drawingHandler.setInputAction((click) => { const pickedObject = this.viewer.scene.pick(click.position); if (this.currentMode === 'select') { this.handleEntitySelection(pickedObject); } else if (this.currentMode === 'marker') { this.addMarker(click.position); } else if (this.currentMode === 'polygon') { this.handlePolygonDrawing(click.position); } else if (this.currentMode === 'polyline') { this.handlePolylineDrawing(click.position); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); // Обработка двойного клика для завершения полигона this.drawingHandler.setInputAction((click) => { if ((this.currentMode === 'polygon') && this.isDrawing && this.currentPolygonPoints.length >= 3) { this.finishPolygonDrawing(); } else if (this.currentMode === 'polyline' && this.isDrawing && this.currentPolylinePoints.length >= 2) { this.finishPolylineDrawing(); } }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK); } handlePolylineDrawing(clickPosition) { const cartesian = this.viewer.camera.pickEllipsoid(clickPosition, this.viewer.scene.globe.ellipsoid); if (!cartesian) return; const cartographic = Cesium.Cartographic.fromCartesian(cartesian); const point = { longitude: Cesium.Math.toDegrees(cartographic.longitude), latitude: Cesium.Math.toDegrees(cartographic.latitude), height: cartographic.height }; if (!this.isDrawing) { this.startPolylineDrawing(point); } else { this.addPointToCurrentPolyline(point); } } startPolylineDrawing(firstPoint) { this.isDrawing = true; this.currentPolylinePoints = [firstPoint]; // Временная точка const firstTempPoint = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(firstPoint.longitude, firstPoint.latitude, firstPoint.height), point: { pixelSize: 8, color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2 } }); this.tempEntities.push(firstTempPoint); // Временная линия (полилиния) this.tempPolyline = this.viewer.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { return this.currentPolylinePoints.map(p => Cesium.Cartesian3.fromDegrees(p.longitude, p.latitude, p.height) ); }, false), width: 3, material: Cesium.Color.RED.withAlpha(0.8), clampToGround: true } }); } addPointToCurrentPolyline(point) { this.currentPolylinePoints.push(point); const tempPoint = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height), point: { pixelSize: 8, color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2 } }); this.tempEntities.push(tempPoint); } finishPolylineDrawing() { if (this.currentPolylinePoints.length < 2) { alert('Линия должна иметь минимум 2 точки'); return; } this.showDescriptionModal().then(description => { if (description !== null) { this.viewer.entities.add({ polyline: { positions: this.currentPolylinePoints.map(p => Cesium.Cartesian3.fromDegrees(p.longitude, p.latitude, p.height) ), width: 4, material: Cesium.Color.RED.withAlpha(0.9), clampToGround: true }, description: description }); } this.cleanupPolylineDrawing(); this.setMode('select'); }); } cleanupPolylineDrawing() { this.isDrawing = false; this.currentPolylinePoints = []; if (this.tempPolyline) { this.viewer.entities.remove(this.tempPolyline); this.tempPolyline = null; } this.clearTempEntities(); } setupMouseMoveHandler() { const mouseMoveHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas); mouseMoveHandler.setInputAction((movement) => { const ellipsoid = this.viewer.scene.globe.ellipsoid; const cartesian = this.viewer.camera.pickEllipsoid(movement.endPosition, ellipsoid); if (cartesian) { const cartographic = Cesium.Cartographic.fromCartesian(cartesian); const lon = Cesium.Math.toDegrees(cartographic.longitude); const lat = Cesium.Math.toDegrees(cartographic.latitude); // Форматируем до 6 знаков после запятой (примерно 10 см точность) const lonStr = lon.toFixed(6); const latStr = lat.toFixed(6); document.getElementById('coordinates').textContent = `${latStr}°, ${lonStr}°`; } else { // Мышь вне земли (океан, край карты и т.д.) document.getElementById('coordinates').textContent = ''; } }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); } handleEntitySelection(pickedObject) { // Снимаем предыдущее выделение if (this.selectedEntity) { this.selectedEntity.outline = false; this.selectedEntity = null; } // Удаляем точки редактирования this.clearEditPoints(); if (pickedObject && pickedObject.id) { this.selectedEntity = pickedObject.id; this.selectedEntity.outline = true; this.selectedEntity.outlineColor = Cesium.Color.YELLOW; this.selectedEntity.outlineWidth = 2; // Если это полигон, показываем точки для редактирования if (this.selectedEntity.polygon) { this.showEditPointsForPolygon(this.selectedEntity); } } } triggerFileImport() { document.getElementById('fileInput').click(); } handleFileSelect(event) { const file = event.target.files[0]; if (!file) return; const fileName = file.name.toLowerCase(); const reader = new FileReader(); reader.onload = (e) => { const content = e.target.result; try { if (fileName.endsWith('.kml')) { this.loadKml(content); } else if (fileName.endsWith('.geojson') || fileName.endsWith('.json')) { this.loadGeoJson(content); } else { alert('Поддерживаются только файлы: .geojson, .json, .kml'); } } catch (error) { console.error('Ошибка импорта:', error); alert('Не удалось загрузить файл. Проверьте его содержимое.'); } // Сброс для повторного выбора того же файла event.target.value = ''; }; reader.readAsText(file); } loadGeoJson(geoJsonString) { const geoJson = JSON.parse(geoJsonString); const dataSource = new Cesium.GeoJsonDataSource(); dataSource.load(geoJson).then(() => { // Переносим все сущности в viewer.entities const entities = dataSource.entities.values; for (let i = 0; i < entities.length; i++) { const entity = entities[i]; // Копируем описание, если нужно this.viewer.entities.add(entity); } // Удаляем dataSource — теперь объекты управляются напрямую this.viewer.dataSources.remove(dataSource); }).catch(error => { console.error('Ошибка GeoJSON:', error); alert('Ошибка при загрузке GeoJSON'); }); } loadKml(kmlString) { const blob = new Blob([kmlString], { type: 'application/vnd.google-earth.kml+xml' }); const url = URL.createObjectURL(blob); Cesium.KmlDataSource.load(url, { camera: this.viewer.camera, canvas: this.viewer.scene.canvas }).then(dataSource => { const entities = dataSource.entities.values; for (let i = 0; i < entities.length; i++) { this.viewer.entities.add(entities[i]); } this.viewer.dataSources.remove(dataSource); URL.revokeObjectURL(url); }).catch(error => { console.error('Ошибка KML:', error); alert('Ошибка при загрузке KML'); URL.revokeObjectURL(url); }); } showExportModal() { document.getElementById('exportModal').style.display = 'block'; } hideExportModal() { document.getElementById('exportModal').style.display = 'none'; } // Вспомогательная функция: получить все сущности как массив getAllEntities() { return this.viewer.entities.values; } // Экспорт в GeoJSON exportAsGeoJson() { const entities = this.getAllEntities(); if (entities.length === 0) { alert('Нет объектов для экспорта'); this.hideExportModal(); return; } const features = []; for (const entity of entities) { let geometry = null; let properties = { name: entity.name || 'Объект', description: entity.description?.getValue?.() || 'Без описания' }; // --- Точка (маркер) --- if (entity.position) { const pos = entity.position.getValue(Cesium.JulianDate.now()); if (pos) { const cart = Cesium.Cartographic.fromCartesian(pos); const lon = Cesium.Math.toDegrees(cart.longitude); const lat = Cesium.Math.toDegrees(cart.latitude); geometry = { type: "Point", coordinates: [lon, lat] }; } } // --- Полигон --- if (entity.polygon) { const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()); if (hierarchy && hierarchy.positions) { const coords = hierarchy.positions.map(p => { const cart = Cesium.Cartographic.fromCartesian(p); return [ Cesium.Math.toDegrees(cart.longitude), Cesium.Math.toDegrees(cart.latitude) ]; }); // Замыкаем полигон if (coords.length > 0 && !Cesium.Cartesian3.equals(coords[0], coords[coords.length - 1])) { coords.push([...coords[0]]); } geometry = { type: "Polygon", coordinates: [coords] }; } } if (geometry) { features.push({ type: "Feature", properties: properties, geometry: geometry }); } } if (features.length === 0) { alert('Не удалось экспортировать объекты: не поддерживаемые типы'); this.hideExportModal(); return; } const geoJson = { type: "FeatureCollection", features: features }; this.downloadFile(JSON.stringify(geoJson, null, 2), 'map_export.geojson', 'application/json'); this.hideExportModal(); } // Экспорт в KML exportAsKml() { const entities = this.getAllEntities(); if (entities.length === 0) { alert('Нет объектов для экспорта'); this.hideExportModal(); return; } let kml = ` Экспорт из CesiumMapEditor `; for (const entity of entities) { const name = (entity.name || 'Объект').replace(//g, '>'); const desc = (entity.description?.getValue?.() || 'Без описания') .replace(//g, '>'); // --- Точка --- if (entity.position) { const pos = entity.position.getValue(Cesium.JulianDate.now()); if (pos) { const cart = Cesium.Cartographic.fromCartesian(pos); const lon = Cesium.Math.toDegrees(cart.longitude); const lat = Cesium.Math.toDegrees(cart.latitude); kml += ` ${name} ${desc} ${lon},${lat} `; } } // --- Полигон --- if (entity.polygon) { const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()); if (hierarchy && hierarchy.positions) { const coords = hierarchy.positions.map(p => { const cart = Cesium.Cartographic.fromCartesian(p); return `${Cesium.Math.toDegrees(cart.longitude)},${Cesium.Math.toDegrees(cart.latitude)}`; }); // Замыкаем полигон if (coords.length > 0) { coords.push(coords[0]); } kml += ` ${name} ${desc} ${coords.join(' ')} `; } } } kml += ` `; this.downloadFile(kml, 'map_export.kml', 'application/vnd.google-earth.kml+xml'); this.hideExportModal(); } // Вспомогательная функция: скачать строку как файл downloadFile(content, filename, mimeType) { const blob = new Blob([content], { type: mimeType }); 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); } addMarker(clickPosition) { // Получаем декартовы координаты на сфере const cartesian = this.viewer.camera.pickEllipsoid(clickPosition, this.viewer.scene.globe.ellipsoid); if (!cartesian) return; // Преобразуем декартовы координаты в картографические (в радианах) const cartographic = Cesium.Cartographic.fromCartesian(cartesian); // Преобразуем радианы в градусы const longitude = Cesium.Math.toDegrees(cartographic.longitude); const latitude = Cesium.Math.toDegrees(cartographic.latitude); const height = cartographic.height; this.showDescriptionModal().then(description => { if (description !== null) { // Используем правильные градусы для создания позиции const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height); const pinBuilder = new Cesium.PinBuilder(); const entity = this.viewer.entities.add({ position: position, billboard: { image: pinBuilder.fromColor(Cesium.Color.RED, 48).toDataURL(), // 48px verticalOrigin: Cesium.VerticalOrigin.BOTTOM, scale: 1.0 }, label: { text: description, font: '12pt sans-serif', pixelOffset: new Cesium.Cartesian2(0, -10), verticalOrigin: Cesium.VerticalOrigin.BOTTOM, fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, showBackground: true, backgroundColor: Cesium.Color.BLACK.withAlpha(0.6) }, description: description }); this.viewer.scene.requestRender(); } }); } handlePolygonDrawing(clickPosition) { const cartesian = this.viewer.camera.pickEllipsoid(clickPosition, this.viewer.scene.globe.ellipsoid); if (!cartesian) return; const cartographic = Cesium.Cartographic.fromCartesian(cartesian); const point = { longitude: Cesium.Math.toDegrees(cartographic.longitude), latitude: Cesium.Math.toDegrees(cartographic.latitude), height: cartographic.height }; if (!this.isDrawing) { // Начинаем новое рисование this.startPolygonDrawing(point); } else { // Добавляем точку к текущему полигону this.addPointToCurrentPolygon(point); } } startPolygonDrawing(firstPoint) { this.isDrawing = true; this.currentPolygonPoints = [firstPoint]; // Добавляем первую временную точку const firstTempPoint = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(firstPoint.longitude, firstPoint.latitude, firstPoint.height), point: { pixelSize: 8, color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2 } }); this.tempEntities.push(firstTempPoint); // Создаём временный полигон this.tempPolygon = this.viewer.entities.add({ polygon: { hierarchy: new Cesium.CallbackProperty(() => { return new Cesium.PolygonHierarchy( this.currentPolygonPoints.map(p => Cesium.Cartesian3.fromDegrees(p.longitude, p.latitude, p.height) ) ); }, false), material: Cesium.Color.BLUE.withAlpha(0.5), outline: true, outlineColor: Cesium.Color.BLACK, extrudedHeight: 0, height: 0 } }); } addPointToCurrentPolygon(point) { this.currentPolygonPoints.push(point); // Добавляем временную точку для визуализации const tempPoint = this.viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height), point: { pixelSize: 8, color: Cesium.Color.YELLOW, outlineColor: Cesium.Color.BLACK, outlineWidth: 2 } }); this.tempEntities.push(tempPoint); } finishPolygonDrawing() { if (this.currentPolygonPoints.length < 3) { alert('Полигон должен иметь 3 точки'); return; } this.showDescriptionModal().then(description => { if (description !== null) { // Создаем финальный полигон const polygonEntity = this.viewer.entities.add({ polygon: { hierarchy: this.currentPolygonPoints.map(p => Cesium.Cartesian3.fromDegrees(p.longitude, p.latitude, p.height) ), material: Cesium.Color.BLUE.withAlpha(0.7), outline: true, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, extrudedHeight: 0, height: 0 }, description: description }); } // Сбрасываем состояние рисования this.cleanupDrawing(); this.setMode('select'); }); } showEditPointsForPolygon(polygonEntity) { const hierarchy = polygonEntity.polygon.hierarchy.getValue(); const positions = hierarchy.positions; positions.forEach((position, index) => { const editPoint = this.viewer.entities.add({ position: position, point: { pixelSize: 10, color: Cesium.Color.RED, outlineColor: Cesium.Color.WHITE, outlineWidth: 2 }, polygonEntity: polygonEntity, pointIndex: index }); this.editPoints.push(editPoint); // В реальной реализации здесь нужно добавить логику перетаскивания точек }); } showDescriptionModal() { return new Promise((resolve) => { const modal = document.getElementById('descriptionModal'); const input = document.getElementById('descriptionInput'); modal.style.display = 'block'; input.value = ''; input.focus(); this.descriptionResolve = resolve; }); } confirmDescription() { const input = document.getElementById('descriptionInput'); const description = input.value.trim(); document.getElementById('descriptionModal').style.display = 'none'; if (this.descriptionResolve) { this.descriptionResolve(description || 'Без описания'); this.descriptionResolve = null; } } cancelDescription() { document.getElementById('descriptionModal').style.display = 'none'; if (this.descriptionResolve) { this.descriptionResolve(null); this.descriptionResolve = null; this.cleanupDrawing(); } } setMode(mode) { // Важно: проверяем, не пытаемся ли установить тот же режим if (this.currentMode === mode) { return; } // Сначала очищаем текущую операцию this.cleanupCurrentOperation(); // Затем устанавливаем новый режим this.currentMode = mode; this.updateUI(); this.updateModeStatus(); this.clearTempEntities(); if (mode === 'select') { this.setupSelectionMode(); } } cancelCurrentOperation() { this.cleanupCurrentOperation(); // Устанавливаем режим select через setMode, но с проверкой if (this.currentMode !== 'select') { this.setMode('select'); } } cleanupCurrentOperation() { if (this.isDrawing) { if (this.currentMode === 'polygon') { this.cleanupDrawing(); } else if (this.currentMode === 'polyline') { this.cleanupPolylineDrawing(); } } this.clearTempEntities(); } cleanupDrawing() { this.isDrawing = false; this.currentPolygonPoints = []; if (this.tempPolygon) { this.viewer.entities.remove(this.tempPolygon); this.tempPolygon = null; } this.clearTempEntities(); } // Остальные методы остаются без изменений... clearTempEntities() { this.tempEntities.forEach(entity => this.viewer.entities.remove(entity)); this.tempEntities = []; } updateUI() { document.querySelectorAll('.tool-btn').forEach(btn => btn.classList.remove('active')); const modeButtons = { 'select': 'selectMode', 'marker': 'markerMode', 'polygon': 'polygonMode', 'polyline': 'polylineMode', }; if (modeButtons[this.currentMode]) { document.getElementById(modeButtons[this.currentMode]).classList.add('active'); } } updateModeStatus() { const modeNames = { 'select': 'Выделение', 'marker': 'Добавление маркеров', 'polygon': 'Рисование полигонов', 'polyline': 'Рисование линий' }; document.getElementById('modeStatus').textContent = `Режим: ${modeNames[this.currentMode]}`; } handleKeyDown(event) { switch (event.key) { case 'Escape': this.cancelCurrentOperation(); break; case 'Delete': if (this.currentMode === 'select' && this.selectedEntity) { this.deleteSelected(); } break; case 's': case 'S': event.preventDefault(); this.setMode('select'); break; case 'm': case 'M': event.preventDefault(); this.setMode('marker'); break; case 'p': case 'P': event.preventDefault(); this.setMode('polygon'); break; case 'l': case 'L': event.preventDefault(); this.setMode('polyline'); break; } } clearEditPoints() { this.editPoints.forEach(point => this.viewer.entities.remove(point)); this.editPoints = []; } deleteSelected() { if (this.selectedEntity) { this.viewer.entities.remove(this.selectedEntity); this.selectedEntity = null; this.clearEditPoints(); } } clearAll() { if (confirm('Вы уверены, что хотите удалить все объекты?')) { this.viewer.entities.removeAll(); this.selectedEntity = null; this.clearEditPoints(); this.cleanupDrawing(); } } setupSelectionMode() { // Дополнительная настройка для режима выделения this.clearEditPoints(); } } window.mapEditor = null; document.addEventListener('DOMContentLoaded', () => { window.mapEditor = new CesiumMapEditor(); document.getElementById('showAllFootprints')?.addEventListener('click', showAllFootprints); document.getElementById('hideAllFootprints')?.addEventListener('click', hideAllFootprints); }); document.getElementById('loadObjectBtn').addEventListener('click', function () { const viewer = window.mapEditor.viewer; const select = document.getElementById('objectSelector'); const sat_id = select.value; if (!sat_id) { alert('Пожалуйста, выберите объект.'); return; } // Формируем URL с параметром name const url = `/api/locations/${encodeURIComponent(sat_id)}/geojson`; fetch(url) .then(response => { if (!response.ok) { throw new Error('Сервер вернул ошибку: ' + response.status); } return response.json(); }) .then(data => { if (!data.features || data.features.length === 0) { alert('Объекты с таким именем не найдены.'); return; } viewer.entities.removeAll(); const freqColorMap = new Map(); // Вспомогательная функция: получить цвет для частоты function getColorForFreq(freq) { if (freqColorMap.has(freq)) { return freqColorMap.get(freq); } // Генерируем новый цвет (но одинаковый для одинаковой freq) // Чтобы цвет был воспроизводимым, можно хешировать freq → RGB const hue = Math.abs(hashCode(String(freq))) % 360; const color = Cesium.Color.fromHsl(hue / 360, 0.7, 0.6); // насыщенный, но не слишком тёмный freqColorMap.set(freq, color); return color; } // Простая хеш-функция для строки → число function hashCode(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Преобразуем в 32-битное целое } return hash; } const pinBuilder = new Cesium.PinBuilder(); data.features.forEach(feature => { const [lon, lat] = feature.geometry.coordinates; let freq = feature.properties.freq; // Получаем цвет для этой частоты const color = getColorForFreq(freq); viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(lon, lat), billboard: { image: pinBuilder.fromColor(color, 36).toDataURL(), verticalOrigin: Cesium.VerticalOrigin.BOTTOM, scale: 1.0 }, description: feature.properties.name, // label: { // text: feature.properties.name, // font: '12px sans-serif', // pixelOffset: new Cesium.Cartesian2(0, -25), // showBackground: true, // backgroundColor: new Cesium.Color(0, 0, 0, 0.6), // disableDepthTestDistance: Number.POSITIVE_INFINITY // }, // Добавим ID для возможного управления позже id: `db-marker-${feature.properties.id}` }); }); }) .catch(err => { console.error('Ошибка:', err); alert('Не удалось загрузить объекты: ' + err.message); }); }); let tileLayers = {}; let currentFootprintLayers = []; // массив активных слоёв const togglesContainer = document.getElementById('footprintToggles'); // Функция: очистить текущие footprint-слои function clearFootprintUIAndLayers(viewer) { // Удаляем слои из Cesium Object.values(tileLayers).forEach(layer => { viewer.imageryLayers.remove(layer); }); tileLayers = {}; // Очищаем чекбоксы togglesContainer.innerHTML = ''; currentFootprintLayers = []; } function escapeHtml(text) { const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.replace(/[&<>"']/g, m => map[m]); } // Функция: загрузить и отобразить footprint'ы для спутника function loadFootprintsForSatellite(satId) { const viewer = window.mapEditor.viewer; if (!satId) { clearFootprintUIAndLayers(viewer); return; } const url = `/api/footprint-names/${encodeURIComponent(satId)}`; fetch(url) .then(response => { if (!response.ok) throw new Error('Ошибка загрузки footprint\'ов'); return response.json(); }) .then(footprints => { if (!Array.isArray(footprints)) { throw new Error('Ожидался массив footprint\'ов'); } // Очищаем старое clearFootprintUIAndLayers(viewer); currentFootprintLayers = footprints; // Создаём новые слои и чекбоксы footprints.forEach(fp => { // 1. Создаём тайловый слой const layer = new Cesium.ImageryLayer( new Cesium.UrlTemplateImageryProvider({ url: `/tiles/${fp.name}/{z}/{x}/{y}.png`, minimumLevel: 0, maximumLevel: 21, credit: 'SatBeams Rendered', tilingScheme: new Cesium.WebMercatorTilingScheme(), hasAlphaChannel: true, // tileDiscardPolicy: new Cesium.DiscardMissingTileImagePolicy({ // missingImageUrl: Cesium.buildModuleUrl('static/cesium/Assets/Textures/transparent.png') // }), }) ); // Слои изначально ВИДИМЫ (можно изменить на false) layer.show = true; viewer.imageryLayers.add(layer); tileLayers[fp.name] = layer; const safeNameAttr = encodeURIComponent(fp.name); // для data-атрибута const safeFullName = escapeHtml(fp.fullname); // для отображения const label = document.createElement('label'); label.style.display = 'block'; label.style.margin = '4px 0'; label.innerHTML = ` ${safeFullName} `; togglesContainer.appendChild(label); // Связываем чекбокс со слоем const checkbox = label.querySelector('input'); checkbox.addEventListener('change', function () { // Декодируем обратно при получении const footprintName = decodeURIComponent(this.dataset.footprint); const layer = tileLayers[footprintName]; if (layer) { layer.show = this.checked; } }); }); }) .catch(err => { console.error('Footprints error:', err); alert('Не удалось загрузить области покрытия: ' + err.message); clearFootprintUIAndLayers(viewer); // на случай ошибки — чистим }); } function showAllFootprints() { Object.values(tileLayers).forEach(layer => { layer.show = true; }); // Синхронизируем чекбоксы document.querySelectorAll('#footprintToggles input[type="checkbox"]').forEach(cb => { cb.checked = true; }); } // Скрыть все footprint-слои function hideAllFootprints() { Object.values(tileLayers).forEach(layer => { layer.show = false; }); document.querySelectorAll('#footprintToggles input[type="checkbox"]').forEach(cb => { cb.checked = false; }); } document.addEventListener('DOMContentLoaded', () => { const select = document.getElementById('objectSelector'); // Загружаем footprint'ы при смене выбора select.addEventListener('change', function () { const satId = this.value; console.log(satId); loadFootprintsForSatellite(satId); }); // Также можно вызвать при первом выборе (если нужно) // Но обычно сначала выбирают → потом нажимают кнопку });