From cfaaae9360946e534c65070b8c784a0af666cd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=88=D0=BA=D0=B8=D0=BD=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9?= Date: Wed, 26 Nov 2025 17:35:59 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D1=83=20=D0=B4=D0=BB=D1=8F=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mainapp/templates/mainapp/data_entry.html | 290 ++++++++++++ .../templates/mainapp/objitem_list.html | 2 +- .../templates/mainapp/satellite_form.html | 416 +++++++++++------- .../templates/mainapp/source_list.html | 3 + dbapp/mainapp/urls.py | 4 + dbapp/mainapp/views/__init__.py | 7 + dbapp/mainapp/views/data_entry.py | 101 +++++ 7 files changed, 671 insertions(+), 152 deletions(-) create mode 100644 dbapp/mainapp/templates/mainapp/data_entry.html create mode 100644 dbapp/mainapp/views/data_entry.py diff --git a/dbapp/mainapp/templates/mainapp/data_entry.html b/dbapp/mainapp/templates/mainapp/data_entry.html new file mode 100644 index 0000000..3529d2e --- /dev/null +++ b/dbapp/mainapp/templates/mainapp/data_entry.html @@ -0,0 +1,290 @@ +{% extends "mainapp/base.html" %} +{% load static %} + +{% block title %}Ввод данных{% endblock %} + +{% block extra_css %} + + +{% endblock %} + +{% block content %} +
+

Ввод данных точек спутников

+ +
+
+
+ + +
+
+ + +
+
+
+ +
+
+
+
Таблица данных 0
+
+
+ + +
+
+ +
+
+
+{% endblock %} + +{% block extra_js %} + + + + +{% endblock %} diff --git a/dbapp/mainapp/templates/mainapp/objitem_list.html b/dbapp/mainapp/templates/mainapp/objitem_list.html index 140e4b1..69bc067 100644 --- a/dbapp/mainapp/templates/mainapp/objitem_list.html +++ b/dbapp/mainapp/templates/mainapp/objitem_list.html @@ -307,7 +307,7 @@ {% include 'mainapp/components/_table_header.html' with label="Част, МГц" field="frequency" sort=sort %} {% include 'mainapp/components/_table_header.html' with label="Полоса, МГц" field="freq_range" sort=sort %} {% include 'mainapp/components/_table_header.html' with label="Поляризация" field="polarization" sort=sort %} - {% include 'mainapp/components/_table_header.html' with label="Сим. V" field="bod_velocity" sort=sort %} + {% include 'mainapp/components/_table_header.html' with label="Сим. скор." field="bod_velocity" sort=sort %} {% include 'mainapp/components/_table_header.html' with label="Модул" field="modulation" sort=sort %} {% include 'mainapp/components/_table_header.html' with label="ОСШ" field="snr" sort=sort %} {% include 'mainapp/components/_table_header.html' with label="Время ГЛ" field="geo_timestamp" sort=sort %} diff --git a/dbapp/mainapp/templates/mainapp/satellite_form.html b/dbapp/mainapp/templates/mainapp/satellite_form.html index c305781..7419c68 100644 --- a/dbapp/mainapp/templates/mainapp/satellite_form.html +++ b/dbapp/mainapp/templates/mainapp/satellite_form.html @@ -274,17 +274,26 @@ const transpondersData = {{ transponders|safe }}; // Chart state let canvas, ctx, container; -let zoomLevel = 1; -let panOffset = 0; +let zoomLevelUL = 1; +let zoomLevelDL = 1; +let panOffsetUL = 0; +let panOffsetDL = 0; let isDragging = false; let dragStartX = 0; -let dragStartOffset = 0; +let dragStartOffsetUL = 0; +let dragStartOffsetDL = 0; +let dragArea = null; // 'uplink' or 'downlink' let hoveredTransponder = null; let transponderRects = []; -// Frequency range -let minFreq, maxFreq, freqRange; -let originalMinFreq, originalMaxFreq, originalFreqRange; +// Frequency ranges for uplink and downlink +let minFreqUL, maxFreqUL, freqRangeUL; +let minFreqDL, maxFreqDL, freqRangeDL; +let originalMinFreqUL, originalMaxFreqUL, originalFreqRangeUL; +let originalMinFreqDL, originalMaxFreqDL, originalFreqRangeDL; + +// Layout variables (need to be global for event handlers) +let uplinkStartY, uplinkHeight, downlinkStartY, downlinkHeight; function initializeFrequencyChart() { if (!transpondersData || transpondersData.length === 0) { @@ -297,36 +306,50 @@ function initializeFrequencyChart() { container = canvas.parentElement; ctx = canvas.getContext('2d'); - // Calculate frequency range (including both downlink and uplink) - minFreq = Infinity; - maxFreq = -Infinity; + // Calculate frequency ranges separately for uplink and downlink + minFreqUL = Infinity; + maxFreqUL = -Infinity; + minFreqDL = Infinity; + maxFreqDL = -Infinity; transpondersData.forEach(t => { // Downlink const dlStartFreq = t.downlink - (t.frequency_range / 2); const dlEndFreq = t.downlink + (t.frequency_range / 2); - minFreq = Math.min(minFreq, dlStartFreq); - maxFreq = Math.max(maxFreq, dlEndFreq); + minFreqDL = Math.min(minFreqDL, dlStartFreq); + maxFreqDL = Math.max(maxFreqDL, dlEndFreq); // Uplink (if exists) if (t.uplink) { const ulStartFreq = t.uplink - (t.frequency_range / 2); const ulEndFreq = t.uplink + (t.frequency_range / 2); - minFreq = Math.min(minFreq, ulStartFreq); - maxFreq = Math.max(maxFreq, ulEndFreq); + minFreqUL = Math.min(minFreqUL, ulStartFreq); + maxFreqUL = Math.max(maxFreqUL, ulEndFreq); } }); - // Add 2% padding - const padding = (maxFreq - minFreq) * 0.04; - minFreq -= padding; - maxFreq += padding; + // Add 2% padding for downlink + const paddingDL = (maxFreqDL - minFreqDL) * 0.04; + minFreqDL -= paddingDL; + maxFreqDL += paddingDL; + + // Add 2% padding for uplink (if exists) + if (maxFreqUL !== -Infinity) { + const paddingUL = (maxFreqUL - minFreqUL) * 0.04; + minFreqUL -= paddingUL; + maxFreqUL += paddingUL; + } // Store original values - originalMinFreq = minFreq; - originalMaxFreq = maxFreq; - originalFreqRange = maxFreq - minFreq; - freqRange = originalFreqRange; + originalMinFreqDL = minFreqDL; + originalMaxFreqDL = maxFreqDL; + originalFreqRangeDL = maxFreqDL - minFreqDL; + freqRangeDL = originalFreqRangeDL; + + originalMinFreqUL = minFreqUL; + originalMaxFreqUL = maxFreqUL; + originalFreqRangeUL = maxFreqUL - minFreqUL; + freqRangeUL = originalFreqRangeUL; // Setup event listeners canvas.addEventListener('wheel', handleWheel, { passive: false }); @@ -359,10 +382,15 @@ function renderChart() { // Layout constants const leftMargin = 60; const rightMargin = 20; - const topMargin = 40; + const topMargin = 60; + const middleMargin = 60; // Space between UL and DL sections const bottomMargin = 40; const chartWidth = width - leftMargin - rightMargin; - const chartHeight = height - topMargin - bottomMargin; + const availableHeight = height - topMargin - middleMargin - bottomMargin; + + // Split available height between UL and DL + uplinkHeight = availableHeight * 0.48; + downlinkHeight = availableHeight * 0.48; // Group transponders by polarization (use first letter only) const polarizationGroups = {}; @@ -377,56 +405,106 @@ function renderChart() { }); const polarizations = Object.keys(polarizationGroups); - // Each polarization gets 2 rows (downlink + uplink) - const rowHeight = chartHeight / (polarizations.length * 2); + const rowHeightUL = uplinkHeight / polarizations.length; + const rowHeightDL = downlinkHeight / polarizations.length; - // Calculate visible frequency range with zoom and pan - const visibleFreqRange = freqRange / zoomLevel; - const centerFreq = (minFreq + maxFreq) / 2; - const visibleMinFreq = centerFreq - visibleFreqRange / 2 + panOffset; - const visibleMaxFreq = centerFreq + visibleFreqRange / 2 + panOffset; + // Calculate visible frequency ranges with zoom and pan for UL + const visibleFreqRangeUL = freqRangeUL / zoomLevelUL; + const centerFreqUL = (minFreqUL + maxFreqUL) / 2; + const visibleMinFreqUL = centerFreqUL - visibleFreqRangeUL / 2 + panOffsetUL; + const visibleMaxFreqUL = centerFreqUL + visibleFreqRangeUL / 2 + panOffsetUL; - // Draw frequency axis + // Calculate visible frequency ranges with zoom and pan for DL + const visibleFreqRangeDL = freqRangeDL / zoomLevelDL; + const centerFreqDL = (minFreqDL + maxFreqDL) / 2; + const visibleMinFreqDL = centerFreqDL - visibleFreqRangeDL / 2 + panOffsetDL; + const visibleMaxFreqDL = centerFreqDL + visibleFreqRangeDL / 2 + panOffsetDL; + + uplinkStartY = topMargin; + downlinkStartY = topMargin + uplinkHeight + middleMargin; + + // Draw UPLINK frequency axis ctx.strokeStyle = '#dee2e6'; ctx.lineWidth = 1; ctx.beginPath(); - ctx.moveTo(leftMargin, topMargin); - ctx.lineTo(width - rightMargin, topMargin); + ctx.moveTo(leftMargin, uplinkStartY); + ctx.lineTo(width - rightMargin, uplinkStartY); ctx.stroke(); - // Draw frequency labels and grid + // Draw UPLINK frequency labels and grid ctx.fillStyle = '#6c757d'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; const numTicks = 10; for (let i = 0; i <= numTicks; i++) { - const freq = visibleMinFreq + (visibleMaxFreq - visibleMinFreq) * i / numTicks; + const freq = visibleMinFreqUL + (visibleMaxFreqUL - visibleMinFreqUL) * i / numTicks; const x = leftMargin + chartWidth * i / numTicks; // Draw tick ctx.beginPath(); - ctx.moveTo(x, topMargin); - ctx.lineTo(x, topMargin - 5); + ctx.moveTo(x, uplinkStartY); + ctx.lineTo(x, uplinkStartY - 5); ctx.stroke(); // Draw grid line ctx.strokeStyle = 'rgba(0, 0, 0, 0.05)'; ctx.beginPath(); - ctx.moveTo(x, topMargin); - ctx.lineTo(x, height - bottomMargin); + ctx.moveTo(x, uplinkStartY); + ctx.lineTo(x, uplinkStartY + uplinkHeight); ctx.stroke(); ctx.strokeStyle = '#dee2e6'; // Draw label - ctx.fillText(freq.toFixed(1), x, topMargin - 10); + ctx.fillText(freq.toFixed(1), x, uplinkStartY - 10); } - // Draw axis title + // Draw UPLINK axis title ctx.fillStyle = '#000'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText('Частота (МГц)', width / 2, topMargin - 25); + ctx.fillText('Uplink Частота (МГц)', width / 2, uplinkStartY - 25); + + // Draw DOWNLINK frequency axis + ctx.strokeStyle = '#dee2e6'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(leftMargin, downlinkStartY); + ctx.lineTo(width - rightMargin, downlinkStartY); + ctx.stroke(); + + // Draw DOWNLINK frequency labels and grid + ctx.fillStyle = '#6c757d'; + ctx.font = '11px sans-serif'; + ctx.textAlign = 'center'; + + for (let i = 0; i <= numTicks; i++) { + const freq = visibleMinFreqDL + (visibleMaxFreqDL - visibleMinFreqDL) * i / numTicks; + const x = leftMargin + chartWidth * i / numTicks; + + // Draw tick + ctx.beginPath(); + ctx.moveTo(x, downlinkStartY); + ctx.lineTo(x, downlinkStartY - 5); + ctx.stroke(); + + // Draw grid line + ctx.strokeStyle = 'rgba(0, 0, 0, 0.05)'; + ctx.beginPath(); + ctx.moveTo(x, downlinkStartY); + ctx.lineTo(x, downlinkStartY + downlinkHeight); + ctx.stroke(); + ctx.strokeStyle = '#dee2e6'; + + // Draw label + ctx.fillText(freq.toFixed(1), x, downlinkStartY - 10); + } + + // Draw DOWNLINK axis title + ctx.fillStyle = '#000'; + ctx.font = 'bold 12px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText('Downlink Частота (МГц)', width / 2, downlinkStartY - 25); // Draw polarization label ctx.save(); @@ -442,102 +520,58 @@ function renderChart() { // Draw transponders polarizations.forEach((pol, index) => { const group = polarizationGroups[pol]; - const downlinkColor = '#0000ff'; //getColor(pol); + const downlinkColor = '#0000ff'; const uplinkColor = '#fd7e14'; - // Downlink row - const downlinkY = topMargin + (index * 2) * rowHeight; - const downlinkBarHeight = rowHeight * 0.8; - const downlinkBarY = downlinkY + (rowHeight - downlinkBarHeight) / 2; + // Uplink row (now on top) + const uplinkY = uplinkStartY + index * rowHeightUL; + const uplinkBarHeight = rowHeightUL * 0.8; + const uplinkBarY = uplinkY + (rowHeightUL - uplinkBarHeight) / 2; - // Uplink row - const uplinkY = topMargin + (index * 2 + 1) * rowHeight; - const uplinkBarHeight = rowHeight * 0.8; - const uplinkBarY = uplinkY + (rowHeight - uplinkBarHeight) / 2; + // Downlink row (now on bottom) + const downlinkY = downlinkStartY + index * rowHeightDL; + const downlinkBarHeight = rowHeightDL * 0.8; + const downlinkBarY = downlinkY + (rowHeightDL - downlinkBarHeight) / 2; - // Draw polarization label (centered between downlink and uplink) + // Draw polarization label for UL section ctx.fillStyle = '#000'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; - const labelY = downlinkY + rowHeight; - ctx.fillText(pol, leftMargin - 25, labelY); + ctx.fillText(pol, leftMargin - 25, uplinkBarY + uplinkBarHeight / 2 + 4); - // Draw "DL" and "UL" labels - ctx.font = '10px sans-serif'; - ctx.fillStyle = '#666'; - ctx.fillText('DL', leftMargin - 5, downlinkBarY + downlinkBarHeight / 2 + 3); - ctx.fillText('UL', leftMargin - 5, uplinkBarY + uplinkBarHeight / 2 + 3); + // Draw polarization label for DL section + ctx.fillText(pol, leftMargin - 25, downlinkBarY + downlinkBarHeight / 2 + 4); - // Draw separator line between DL and UL - ctx.strokeStyle = '#dee2e6'; - ctx.lineWidth = 1; - ctx.beginPath(); - ctx.moveTo(leftMargin, uplinkY); - ctx.lineTo(width - rightMargin, uplinkY); - ctx.stroke(); + // Draw separator lines between polarization groups + if (index < polarizations.length - 1) { + ctx.strokeStyle = '#adb5bd'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(leftMargin, uplinkY + rowHeightUL); + ctx.lineTo(width - rightMargin, uplinkY + rowHeightUL); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(leftMargin, downlinkY + rowHeightDL); + ctx.lineTo(width - rightMargin, downlinkY + rowHeightDL); + ctx.stroke(); + } - // Draw downlink transponders - group.forEach(t => { - const startFreq = t.downlink - (t.frequency_range / 2); - const endFreq = t.downlink + (t.frequency_range / 2); - - // Check if transponder is visible - if (endFreq < visibleMinFreq || startFreq > visibleMaxFreq) { - return; - } - - const x1 = leftMargin + ((startFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth; - const x2 = leftMargin + ((endFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth; - const barWidth = x2 - x1; - - if (barWidth < 1) return; - - const isHovered = hoveredTransponder && hoveredTransponder.transponder.name === t.name; - - // Draw downlink bar - ctx.fillStyle = downlinkColor; - ctx.fillRect(x1, downlinkBarY, barWidth, downlinkBarHeight); - - // Draw border (thicker if hovered) - ctx.strokeStyle = isHovered ? '#000' : '#fff'; - ctx.lineWidth = isHovered ? 3 : 1; - ctx.strokeRect(x1, downlinkBarY, barWidth, downlinkBarHeight); - - // Draw name if there's space - if (barWidth > 40) { - ctx.fillStyle = '#fff'; - ctx.font = isHovered ? 'bold 10px sans-serif' : '9px sans-serif'; - ctx.textAlign = 'center'; - ctx.fillText(t.name, x1 + barWidth / 2, downlinkBarY + downlinkBarHeight / 2 + 3); - } - - // Store for hover detection - transponderRects.push({ - x: x1, - y: downlinkBarY, - width: barWidth, - height: downlinkBarHeight, - transponder: t, - type: 'downlink', - centerX: x1 + barWidth / 2 - }); - }); - - // Draw uplink transponders + // Draw uplink transponders (now first, on top) group.forEach(t => { if (!t.uplink) return; // Skip if no uplink data const startFreq = t.uplink - (t.frequency_range / 2); const endFreq = t.uplink + (t.frequency_range / 2); - // Check if transponder is visible - if (endFreq < visibleMinFreq || startFreq > visibleMaxFreq) { + // Check if transponder is visible in UL range + if (endFreq < visibleMinFreqUL || startFreq > visibleMaxFreqUL) { return; } - // Calculate position - const x1 = leftMargin + ((startFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth; - const x2 = leftMargin + ((endFreq - visibleMinFreq) / (visibleMaxFreq - visibleMinFreq)) * chartWidth; + // Calculate position using UL axis + const x1 = leftMargin + ((startFreq - visibleMinFreqUL) / (visibleMaxFreqUL - visibleMinFreqUL)) * chartWidth; + const x2 = leftMargin + ((endFreq - visibleMinFreqUL) / (visibleMaxFreqUL - visibleMinFreqUL)) * chartWidth; const barWidth = x2 - x1; // Skip if too small @@ -574,16 +608,53 @@ function renderChart() { }); }); - // Draw separator line after each polarization group (except last) - if (index < polarizations.length - 1) { - const separatorY = topMargin + (index * 2 + 2) * rowHeight; - ctx.strokeStyle = '#adb5bd'; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(leftMargin, separatorY); - ctx.lineTo(width - rightMargin, separatorY); - ctx.stroke(); - } + // Draw downlink transponders (now second, on bottom) + group.forEach(t => { + const startFreq = t.downlink - (t.frequency_range / 2); + const endFreq = t.downlink + (t.frequency_range / 2); + + // Check if transponder is visible in DL range + if (endFreq < visibleMinFreqDL || startFreq > visibleMaxFreqDL) { + return; + } + + // Calculate position using DL axis + const x1 = leftMargin + ((startFreq - visibleMinFreqDL) / (visibleMaxFreqDL - visibleMinFreqDL)) * chartWidth; + const x2 = leftMargin + ((endFreq - visibleMinFreqDL) / (visibleMaxFreqDL - visibleMinFreqDL)) * chartWidth; + const barWidth = x2 - x1; + + if (barWidth < 1) return; + + const isHovered = hoveredTransponder && hoveredTransponder.transponder.name === t.name; + + // Draw downlink bar + ctx.fillStyle = downlinkColor; + ctx.fillRect(x1, downlinkBarY, barWidth, downlinkBarHeight); + + // Draw border (thicker if hovered) + ctx.strokeStyle = isHovered ? '#000' : '#fff'; + ctx.lineWidth = isHovered ? 3 : 1; + ctx.strokeRect(x1, downlinkBarY, barWidth, downlinkBarHeight); + + // Draw name if there's space + if (barWidth > 40) { + ctx.fillStyle = '#fff'; + ctx.font = isHovered ? 'bold 10px sans-serif' : '9px sans-serif'; + ctx.textAlign = 'center'; + ctx.fillText(t.name, x1 + barWidth / 2, downlinkBarY + downlinkBarHeight / 2 + 3); + } + + // Store for hover detection + transponderRects.push({ + x: x1, + y: downlinkBarY, + width: barWidth, + height: downlinkBarHeight, + transponder: t, + type: 'downlink', + centerX: x1 + barWidth / 2 + }); + }); }); // Draw connection line between downlink and uplink when hovering @@ -694,24 +765,50 @@ function drawTooltip(rectInfo) { function handleWheel(e) { e.preventDefault(); - const delta = e.deltaY > 0 ? 0.9 : 1.1; - const newZoom = Math.max(1, Math.min(20, zoomLevel * delta)); + const rect = canvas.getBoundingClientRect(); + const mouseY = e.clientY - rect.top; - if (newZoom !== zoomLevel) { - zoomLevel = newZoom; - - // Adjust pan to keep center - const maxPan = (originalFreqRange * (zoomLevel - 1)) / (2 * zoomLevel); - panOffset = Math.max(-maxPan, Math.min(maxPan, panOffset)); - - renderChart(); + // Determine which area we're zooming + const isUplinkArea = mouseY < (uplinkStartY + uplinkHeight); + + const delta = e.deltaY > 0 ? 0.9 : 1.1; + + if (isUplinkArea) { + const newZoom = Math.max(1, Math.min(20, zoomLevelUL * delta)); + if (newZoom !== zoomLevelUL) { + zoomLevelUL = newZoom; + + // Adjust pan to keep center + const maxPan = (originalFreqRangeUL * (zoomLevelUL - 1)) / (2 * zoomLevelUL); + panOffsetUL = Math.max(-maxPan, Math.min(maxPan, panOffsetUL)); + + renderChart(); + } + } else { + const newZoom = Math.max(1, Math.min(20, zoomLevelDL * delta)); + if (newZoom !== zoomLevelDL) { + zoomLevelDL = newZoom; + + // Adjust pan to keep center + const maxPan = (originalFreqRangeDL * (zoomLevelDL - 1)) / (2 * zoomLevelDL); + panOffsetDL = Math.max(-maxPan, Math.min(maxPan, panOffsetDL)); + + renderChart(); + } } } function handleMouseDown(e) { + const rect = canvas.getBoundingClientRect(); + const mouseY = e.clientY - rect.top; + + // Determine which area we're dragging + dragArea = mouseY < (uplinkStartY + uplinkHeight) ? 'uplink' : 'downlink'; + isDragging = true; dragStartX = e.clientX; - dragStartOffset = panOffset; + dragStartOffsetUL = panOffsetUL; + dragStartOffsetDL = panOffsetDL; canvas.style.cursor = 'grabbing'; } @@ -722,12 +819,22 @@ function handleMouseMove(e) { if (isDragging) { const dx = e.clientX - dragStartX; - const freqPerPixel = (freqRange / zoomLevel) / (rect.width - 80); - panOffset = dragStartOffset - dx * freqPerPixel; - // Limit pan - const maxPan = (originalFreqRange * (zoomLevel - 1)) / (2 * zoomLevel); - panOffset = Math.max(-maxPan, Math.min(maxPan, panOffset)); + if (dragArea === 'uplink') { + const freqPerPixel = (freqRangeUL / zoomLevelUL) / (rect.width - 80); + panOffsetUL = dragStartOffsetUL - dx * freqPerPixel; + + // Limit pan + const maxPan = (originalFreqRangeUL * (zoomLevelUL - 1)) / (2 * zoomLevelUL); + panOffsetUL = Math.max(-maxPan, Math.min(maxPan, panOffsetUL)); + } else { + const freqPerPixel = (freqRangeDL / zoomLevelDL) / (rect.width - 80); + panOffsetDL = dragStartOffsetDL - dx * freqPerPixel; + + // Limit pan + const maxPan = (originalFreqRangeDL * (zoomLevelDL - 1)) / (2 * zoomLevelDL); + panOffsetDL = Math.max(-maxPan, Math.min(maxPan, panOffsetDL)); + } renderChart(); } else { @@ -767,20 +874,27 @@ function handleMouseLeave() { } function resetZoom() { - zoomLevel = 1; - panOffset = 0; + zoomLevelUL = 1; + zoomLevelDL = 1; + panOffsetUL = 0; + panOffsetDL = 0; renderChart(); } function zoomIn() { - zoomLevel = Math.min(20, zoomLevel * 1.2); + zoomLevelUL = Math.min(20, zoomLevelUL * 1.2); + zoomLevelDL = Math.min(20, zoomLevelDL * 1.2); renderChart(); } function zoomOut() { - zoomLevel = Math.max(1, zoomLevel / 1.2); - if (zoomLevel === 1) { - panOffset = 0; + zoomLevelUL = Math.max(1, zoomLevelUL / 1.2); + zoomLevelDL = Math.max(1, zoomLevelDL / 1.2); + if (zoomLevelUL === 1) { + panOffsetUL = 0; + } + if (zoomLevelDL === 1) { + panOffsetDL = 0; } renderChart(); } diff --git a/dbapp/mainapp/templates/mainapp/source_list.html b/dbapp/mainapp/templates/mainapp/source_list.html index 24b4920..255af54 100644 --- a/dbapp/mainapp/templates/mainapp/source_list.html +++ b/dbapp/mainapp/templates/mainapp/source_list.html @@ -79,6 +79,9 @@ Создать {% endif %} + + Ввод данных + Excel diff --git a/dbapp/mainapp/urls.py b/dbapp/mainapp/urls.py index 01ba367..ce62e02 100644 --- a/dbapp/mainapp/urls.py +++ b/dbapp/mainapp/urls.py @@ -8,6 +8,7 @@ from .views import ( AddTranspondersView, # ClusterTestView, ClearLyngsatCacheView, + DataEntryView, DeleteSelectedObjectsView, DeleteSelectedSourcesView, DeleteSelectedTranspondersView, @@ -36,6 +37,7 @@ from .views import ( SatelliteListView, SatelliteCreateView, SatelliteUpdateView, + SearchObjItemAPIView, ShowMapView, ShowSelectedObjectsMapView, ShowSourcesMapView, @@ -118,5 +120,7 @@ urlpatterns = [ path('api/update-object-mark/', UpdateObjectMarkView.as_view(), name='update_object_mark'), path('kubsat/', KubsatView.as_view(), name='kubsat'), path('kubsat/export/', KubsatExportView.as_view(), name='kubsat_export'), + path('data-entry/', DataEntryView.as_view(), name='data_entry'), + path('api/search-objitem/', SearchObjItemAPIView.as_view(), name='search_objitem_api'), path('logout/', custom_logout, name='logout'), ] \ No newline at end of file diff --git a/dbapp/mainapp/views/__init__.py b/dbapp/mainapp/views/__init__.py index 6c1185d..6d1d125 100644 --- a/dbapp/mainapp/views/__init__.py +++ b/dbapp/mainapp/views/__init__.py @@ -59,6 +59,10 @@ from .kubsat import ( KubsatView, KubsatExportView, ) +from .data_entry import ( + DataEntryView, + SearchObjItemAPIView, +) __all__ = [ # Base @@ -122,4 +126,7 @@ __all__ = [ # Kubsat 'KubsatView', 'KubsatExportView', + # Data Entry + 'DataEntryView', + 'SearchObjItemAPIView', ] diff --git a/dbapp/mainapp/views/data_entry.py b/dbapp/mainapp/views/data_entry.py new file mode 100644 index 0000000..dfa5248 --- /dev/null +++ b/dbapp/mainapp/views/data_entry.py @@ -0,0 +1,101 @@ +""" +Data entry view for satellite points. +""" +import json +import re +from datetime import datetime + +from django.contrib.auth.mixins import LoginRequiredMixin +from django.db.models import Q +from django.http import JsonResponse +from django.shortcuts import render +from django.views import View + +from ..models import ObjItem, Satellite + + +class DataEntryView(LoginRequiredMixin, View): + """ + View for data entry form with Tabulator table. + """ + + def get(self, request): + # Get satellites that have points + satellites = Satellite.objects.filter( + parameters__objitem__isnull=False + ).distinct().order_by('name') + + context = { + 'satellites': satellites, + "full_width_page": True + } + + return render(request, 'mainapp/data_entry.html', context) + + +class SearchObjItemAPIView(LoginRequiredMixin, View): + """ + API endpoint for searching ObjItem by name. + Returns first matching ObjItem with all required data. + """ + + def get(self, request): + name = request.GET.get('name', '').strip() + satellite_id = request.GET.get('satellite_id', '').strip() + + if not name: + return JsonResponse({'error': 'Name parameter is required'}, status=400) + + # Build query + query = Q(name__iexact=name) + + # Add satellite filter if provided + if satellite_id: + try: + sat_id = int(satellite_id) + query &= Q(parameter_obj__id_satellite_id=sat_id) + except (ValueError, TypeError): + pass + + # Search for ObjItem + objitem = ObjItem.objects.filter(query).select_related( + 'parameter_obj', + 'parameter_obj__id_satellite', + 'parameter_obj__polarization', + 'parameter_obj__modulation', + 'parameter_obj__standard', + 'geo_obj' + ).prefetch_related( + 'geo_obj__mirrors' + ).first() + + if not objitem: + return JsonResponse({'found': False}) + + # Prepare response data + data = { + 'found': True, + 'frequency': None, + 'freq_range': None, + 'bod_velocity': None, + 'modulation': None, + 'snr': None, + 'mirrors': None, + } + + # Get parameter data + if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj: + param = objitem.parameter_obj + data['frequency'] = param.frequency if param.frequency else None + data['freq_range'] = param.freq_range if param.freq_range else None + data['bod_velocity'] = param.bod_velocity if param.bod_velocity else None + data['modulation'] = param.modulation.name if param.modulation else None + data['snr'] = param.snr if param.snr else None + + # Get mirrors data + if hasattr(objitem, 'geo_obj') and objitem.geo_obj: + mirrors = objitem.geo_obj.mirrors.all() + if mirrors: + data['mirrors'] = ', '.join([m.name for m in mirrors]) + + return JsonResponse(data)