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);
});
// Также можно вызвать при первом выборе (если нужно)
// Но обычно сначала выбирают → потом нажимают кнопку
});