Files
dbstorage/dbapp/static/mapsapp/main.js
2025-10-24 13:08:08 +03:00

1113 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = `<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Экспорт из CesiumMapEditor</name>
`;
for (const entity of entities) {
const name = (entity.name || 'Объект').replace(/</g, '<').replace(/>/g, '>');
const desc = (entity.description?.getValue?.() || 'Без описания')
.replace(/</g, '<').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 += `
<Placemark>
<name>${name}</name>
<description>${desc}</description>
<Point>
<coordinates>${lon},${lat}</coordinates>
</Point>
</Placemark>`;
}
}
// --- Полигон ---
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 += `
<Placemark>
<name>${name}</name>
<description>${desc}</description>
<Polygon>
<outerBoundaryIs>
<LinearRing>
<coordinates>
${coords.join(' ')}
</coordinates>
</LinearRing>
</outerBoundaryIs>
</Polygon>
</Placemark>`;
}
}
}
kml += `
</Document>
</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 = {
'&': '&amp;',
'<': '<',
'>': '>',
'"': '&quot;',
"'": '&#039;'
};
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 = `
<input type="checkbox"
data-footprint="${safeNameAttr}"
checked>
${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);
});
// Также можно вызвать при первом выборе (если нужно)
// Но обычно сначала выбирают → потом нажимают кнопку
});