Расчет с выравниванием СДИ
This commit is contained in:
+40
-4
@@ -201,6 +201,28 @@ abstract class AbstractPuudCalculator(
|
|||||||
protected fun sdiForWd(wd: Vector3D, sickle: Boolean = false): Double =
|
protected fun sdiForWd(wd: Vector3D, sickle: Boolean = false): Double =
|
||||||
(if (sickle) wd.z else wd.x) * config.focus
|
(if (sickle) wd.z else wd.x) * config.focus
|
||||||
|
|
||||||
|
protected fun edgeLineOfSightVectorsInConnected(): Pair<Vector3D, Vector3D>? {
|
||||||
|
if (config.focus <= EPS || config.dlOep <= EPS) return null
|
||||||
|
val leftOffsetMm = config.dxOep - config.dlOep / 2.0
|
||||||
|
val rightOffsetMm = config.dxOep + config.dlOep / 2.0
|
||||||
|
return lineOfSightByFocalPlaneZ(leftOffsetMm) to lineOfSightByFocalPlaneZ(rightOffsetMm)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun edgeSdiValues(
|
||||||
|
point: OrbitalPoint,
|
||||||
|
orientation: Orientation,
|
||||||
|
omegaConnected: Vector3D,
|
||||||
|
sickle: Boolean = false,
|
||||||
|
): Pair<Double, Double>? {
|
||||||
|
val (leftLine, rightLine) = edgeLineOfSightVectorsInConnected() ?: return null
|
||||||
|
val leftSdi = sdiForWd(wd(point, orientation, omegaConnected, leftLine), sickle)
|
||||||
|
val rightSdi = sdiForWd(wd(point, orientation, omegaConnected, rightLine), sickle)
|
||||||
|
return leftSdi to rightSdi
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun lineOfSightByFocalPlaneZ(offsetMm: Double): Vector3D =
|
||||||
|
Vector3D(0.0, -config.focus, offsetMm).normSafe()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Аналог AbstractAISTPUUD::wd. Возвращает W/D в связанной СК для единственной центральной линии визирования.
|
* Аналог AbstractAISTPUUD::wd. Возвращает W/D в связанной СК для единственной центральной линии визирования.
|
||||||
*
|
*
|
||||||
@@ -209,16 +231,24 @@ abstract class AbstractPuudCalculator(
|
|||||||
* добавляется радиальная составляющая изменения наклонной дальности, чтобы точка оставалась
|
* добавляется радиальная составляющая изменения наклонной дальности, чтобы точка оставалась
|
||||||
* на поверхности эллипсоида. Такая схема соответствует формулам из OrbitalMotion::AbstractAISTPUUD::wd.
|
* на поверхности эллипсоида. Такая схема соответствует формулам из OrbitalMotion::AbstractAISTPUUD::wd.
|
||||||
*/
|
*/
|
||||||
protected fun wd(point: OrbitalPoint, orientation: Orientation, omegaConnected: Vector3D): Vector3D {
|
protected fun wd(point: OrbitalPoint, orientation: Orientation, omegaConnected: Vector3D): Vector3D =
|
||||||
|
wd(point, orientation, omegaConnected, lineOfSightVectorInConnected())
|
||||||
|
|
||||||
|
protected fun wd(
|
||||||
|
point: OrbitalPoint,
|
||||||
|
orientation: Orientation,
|
||||||
|
omegaConnected: Vector3D,
|
||||||
|
lineConnected: Vector3D,
|
||||||
|
): Vector3D {
|
||||||
val ask = astro.grinvToASK(point)
|
val ask = astro.grinvToASK(point)
|
||||||
val lineConnected = lineOfSightVectorInConnected()
|
val lineInConnected = lineConnected.normSafe()
|
||||||
val connectedToOrbit = orientationMatrix(orientation)
|
val connectedToOrbit = orientationMatrix(orientation)
|
||||||
val absToOrbBook = absToOrbBookMatrix(ask.r, ask.v)
|
val absToOrbBook = absToOrbBookMatrix(ask.r, ask.v)
|
||||||
val orbitBookToAbs = absToOrbBook.transpose()
|
val orbitBookToAbs = absToOrbBook.transpose()
|
||||||
val orbitToOrbitBook = orbBookToOrbMatrix().transpose()
|
val orbitToOrbitBook = orbBookToOrbMatrix().transpose()
|
||||||
val absToGsk = Matrix3D().also { it.makeOzMatrix(-astro.si2000(point.t)) }
|
val absToGsk = Matrix3D().also { it.makeOzMatrix(-astro.si2000(point.t)) }
|
||||||
|
|
||||||
val lineGsk = (absToGsk * orbitBookToAbs * orbitToOrbitBook * connectedToOrbit * lineConnected).normSafe()
|
val lineGsk = (absToGsk * orbitBookToAbs * orbitToOrbitBook * connectedToOrbit * lineInConnected).normSafe()
|
||||||
if (lineGsk.module() < EPS) return Vector3D()
|
if (lineGsk.module() < EPS) return Vector3D()
|
||||||
|
|
||||||
val earth = astro.earth
|
val earth = astro.earth
|
||||||
@@ -310,6 +340,9 @@ abstract class AbstractPuudCalculator(
|
|||||||
val eps = previous?.let { if (abs(t - it.t) > EPS) (omega - it.omega) / (t - it.t) else Vector3D() } ?: Vector3D()
|
val eps = previous?.let { if (abs(t - it.t) > EPS) (omega - it.omega) / (t - it.t) else Vector3D() } ?: Vector3D()
|
||||||
val ground = pointOnEarth(orbital, orientation)
|
val ground = pointOnEarth(orbital, orientation)
|
||||||
val wd = wd(orbital, orientation, omega)
|
val wd = wd(orbital, orientation, omega)
|
||||||
|
val edgeSdi = edgeSdiValues(orbital, orientation, omega, sickle)
|
||||||
|
val centerSdi = sdiForWd(wd, sickle)
|
||||||
|
val sdiSpread = edgeSdi?.let { maxOf(abs(it.first - centerSdi), abs(it.second - centerSdi)) }
|
||||||
return AngularMotionPoint(
|
return AngularMotionPoint(
|
||||||
t = t,
|
t = t,
|
||||||
orbitalPoint = orbital,
|
orbitalPoint = orbital,
|
||||||
@@ -319,7 +352,10 @@ abstract class AbstractPuudCalculator(
|
|||||||
eps = eps,
|
eps = eps,
|
||||||
quaternion = q,
|
quaternion = q,
|
||||||
wd = wd,
|
wd = wd,
|
||||||
sdi = sdiForWd(wd, sickle),
|
sdi = centerSdi,
|
||||||
|
sdiLeft = edgeSdi?.first,
|
||||||
|
sdiRight = edgeSdi?.second,
|
||||||
|
sdiSpread = sdiSpread,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
@@ -76,6 +76,12 @@ data class AngularMotionPoint(
|
|||||||
val quaternion: Quaternion3D = Quaternion3D(1.0, 0.0, 0.0, 0.0),
|
val quaternion: Quaternion3D = Quaternion3D(1.0, 0.0, 0.0, 0.0),
|
||||||
val wd: Vector3D = Vector3D(),
|
val wd: Vector3D = Vector3D(),
|
||||||
val sdi: Double = 0.0,
|
val sdi: Double = 0.0,
|
||||||
|
/** СДИ на левом краю линейки ОЭП, мм/с. */
|
||||||
|
val sdiLeft: Double? = null,
|
||||||
|
/** СДИ на правом краю линейки ОЭП, мм/с. */
|
||||||
|
val sdiRight: Double? = null,
|
||||||
|
/** Максимальное отклонение краевой СДИ от центральной, мм/с. */
|
||||||
|
val sdiSpread: Double? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Полный результат расчета режима углового движения. */
|
/** Полный результат расчета режима углового движения. */
|
||||||
|
|||||||
+8
-2
@@ -152,6 +152,9 @@ open class AzimuthPUUD(
|
|||||||
val ground = pointOnEarth(orbital, orientation)
|
val ground = pointOnEarth(orbital, orientation)
|
||||||
val q = quaternionFor(orbital, orientation)
|
val q = quaternionFor(orbital, orientation)
|
||||||
val wd = wd(orbital, orientation, omega)
|
val wd = wd(orbital, orientation, omega)
|
||||||
|
val edgeSdi = edgeSdiValues(orbital, orientation, omega, sickle)
|
||||||
|
val centerSdi = sdiForWd(wd, sickle)
|
||||||
|
val sdiSpread = edgeSdi?.let { maxOf(abs(it.first - centerSdi), abs(it.second - centerSdi)) }
|
||||||
return AngularMotionPoint(
|
return AngularMotionPoint(
|
||||||
t = t,
|
t = t,
|
||||||
orbitalPoint = orbital,
|
orbitalPoint = orbital,
|
||||||
@@ -161,11 +164,14 @@ open class AzimuthPUUD(
|
|||||||
eps = eps,
|
eps = eps,
|
||||||
quaternion = q,
|
quaternion = q,
|
||||||
wd = wd,
|
wd = wd,
|
||||||
sdi = sdiForWd(wd, sickle),
|
sdi = centerSdi,
|
||||||
|
sdiLeft = edgeSdi?.first,
|
||||||
|
sdiRight = edgeSdi?.second,
|
||||||
|
sdiSpread = sdiSpread,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visirToConnected(omegaVisir: Vector3D): Vector3D =
|
protected fun visirToConnected(omegaVisir: Vector3D): Vector3D =
|
||||||
conToVisirMatrix().transpose() * omegaVisir
|
conToVisirMatrix().transpose() * omegaVisir
|
||||||
|
|
||||||
protected fun initialVisirQuaternion(
|
protected fun initialVisirQuaternion(
|
||||||
|
|||||||
+46
-1
@@ -66,7 +66,47 @@ class SmoothSDIPUUD(
|
|||||||
vsOptic.y - sdi / config.focus,
|
vsOptic.y - sdi / config.focus,
|
||||||
)
|
)
|
||||||
val omegaInVisir = visToOptic * programmed + (liv.inverse() * omegaEarth)
|
val omegaInVisir = visToOptic * programmed + (liv.inverse() * omegaEarth)
|
||||||
return omegaInVisir
|
return alignEdgeSdi(time, liv, omegaInVisir, sdi)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вариант №3 требует не только Wz/D = 0 и заданную СДИ на центральной ЦЛВ,
|
||||||
|
* но и минимизацию изменения СДИ по ширине полосы. Центральная ЦЛВ не чувствительна
|
||||||
|
* к вращению вокруг себя, зато крайние лучи линейки ОЭП чувствительны. Поэтому
|
||||||
|
* подбираем добавку к компоненте угловой скорости вокруг ЦЛВ так, чтобы СДИ на
|
||||||
|
* левом и правом краях была максимально близка к заданной.
|
||||||
|
*/
|
||||||
|
private fun alignEdgeSdi(
|
||||||
|
time: Double,
|
||||||
|
liv: Quaternion3D,
|
||||||
|
omegaInVisir: Vector3D,
|
||||||
|
targetSdi: Double,
|
||||||
|
): Vector3D {
|
||||||
|
if (config.dlOep <= EPS || config.focus <= EPS) return omegaInVisir
|
||||||
|
|
||||||
|
val orbital = pointAt(time)
|
||||||
|
val orientation = orientationFromVisirQuaternion(orbital, liv)
|
||||||
|
|
||||||
|
fun edgeSdiFor(omegaVisir: Vector3D): Pair<Double, Double>? =
|
||||||
|
edgeSdiValues(orbital, orientation, visirToConnected(omegaVisir), sickle = false)
|
||||||
|
|
||||||
|
val baseEdges = edgeSdiFor(omegaInVisir) ?: return omegaInVisir
|
||||||
|
val probeOmega = Vector3D(omegaInVisir.x + LINE_SPIN_PROBE, omegaInVisir.y, omegaInVisir.z)
|
||||||
|
val probeEdges = edgeSdiFor(probeOmega) ?: return omegaInVisir
|
||||||
|
|
||||||
|
val leftK = (probeEdges.first - baseEdges.first) / LINE_SPIN_PROBE
|
||||||
|
val rightK = (probeEdges.second - baseEdges.second) / LINE_SPIN_PROBE
|
||||||
|
val denom = leftK * leftK + rightK * rightK
|
||||||
|
if (denom < EPS || !denom.isFinite()) return omegaInVisir
|
||||||
|
|
||||||
|
val correction = -((baseEdges.first - targetSdi) * leftK + (baseEdges.second - targetSdi) * rightK) / denom
|
||||||
|
if (!correction.isFinite()) return omegaInVisir
|
||||||
|
|
||||||
|
return Vector3D(
|
||||||
|
omegaInVisir.x + correction.coerceIn(-MAX_LINE_SPIN_CORRECTION, MAX_LINE_SPIN_CORRECTION),
|
||||||
|
omegaInVisir.y,
|
||||||
|
omegaInVisir.z,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun horizontalGeodesicToAbs(
|
private fun horizontalGeodesicToAbs(
|
||||||
@@ -93,4 +133,9 @@ class SmoothSDIPUUD(
|
|||||||
gskToAbs * upGsk,
|
gskToAbs * upGsk,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
private companion object {
|
||||||
|
const val LINE_SPIN_PROBE = 1.0e-5
|
||||||
|
const val MAX_LINE_SPIN_CORRECTION = 0.05
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -47,4 +47,7 @@ data class AngularMotionPointDTO(
|
|||||||
val wdY: Double,
|
val wdY: Double,
|
||||||
val wdZ: Double,
|
val wdZ: Double,
|
||||||
val sdi: Double,
|
val sdi: Double,
|
||||||
|
val sdiLeft: Double?,
|
||||||
|
val sdiRight: Double?,
|
||||||
|
val sdiSpread: Double?,
|
||||||
)
|
)
|
||||||
|
|||||||
+3
@@ -117,6 +117,9 @@ class AngularMotionService(
|
|||||||
wdY = point.wd.y,
|
wdY = point.wd.y,
|
||||||
wdZ = point.wd.z,
|
wdZ = point.wd.z,
|
||||||
sdi = point.sdi,
|
sdi = point.sdi,
|
||||||
|
sdiLeft = point.sdiLeft,
|
||||||
|
sdiRight = point.sdiRight,
|
||||||
|
sdiSpread = point.sdiSpread,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+5
-2
@@ -451,7 +451,7 @@
|
|||||||
el('angular-motion-export-csv').disabled = rows.length === 0;
|
el('angular-motion-export-csv').disabled = rows.length === 0;
|
||||||
const body = el('angular-motion-result-body');
|
const body = el('angular-motion-result-body');
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
body.innerHTML = '<tr><td colspan="14" class="text-center text-muted py-4">Нет данных</td></tr>';
|
body.innerHTML = '<tr><td colspan="17" class="text-center text-muted py-4">Нет данных</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
body.innerHTML = rows.map(point => `
|
body.innerHTML = rows.map(point => `
|
||||||
@@ -470,6 +470,9 @@
|
|||||||
<td>${escapeHtml(formatNumber(point.wdY, 7))}</td>
|
<td>${escapeHtml(formatNumber(point.wdY, 7))}</td>
|
||||||
<td>${escapeHtml(formatNumber(point.wdZ, 7))}</td>
|
<td>${escapeHtml(formatNumber(point.wdZ, 7))}</td>
|
||||||
<td>${escapeHtml(formatNumber(point.sdi, 4))}</td>
|
<td>${escapeHtml(formatNumber(point.sdi, 4))}</td>
|
||||||
|
<td>${escapeHtml(formatNumber(point.sdiLeft, 4))}</td>
|
||||||
|
<td>${escapeHtml(formatNumber(point.sdiRight, 4))}</td>
|
||||||
|
<td>${escapeHtml(formatNumber(point.sdiSpread, 4))}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`).join('');
|
||||||
drawMap(true);
|
drawMap(true);
|
||||||
@@ -500,7 +503,7 @@
|
|||||||
function exportCsv() {
|
function exportCsv() {
|
||||||
const rows = Array.isArray(state.result?.points) ? state.result.points : [];
|
const rows = Array.isArray(state.result?.points) ? state.result.points : [];
|
||||||
if (rows.length === 0) return;
|
if (rows.length === 0) return;
|
||||||
const header = ['time', 'revolution', 'tangDeg', 'krenDeg', 'riskDeg', 'groundLatitudeDeg', 'groundLongitudeDeg', 'omegaX', 'omegaY', 'omegaZ', 'wdX', 'wdY', 'wdZ', 'sdi'];
|
const header = ['time', 'revolution', 'tangDeg', 'krenDeg', 'riskDeg', 'groundLatitudeDeg', 'groundLongitudeDeg', 'omegaX', 'omegaY', 'omegaZ', 'wdX', 'wdY', 'wdZ', 'sdi', 'sdiLeft', 'sdiRight', 'sdiSpread'];
|
||||||
const lines = [header.join(';')];
|
const lines = [header.join(';')];
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
lines.push(header.map(key => row[key] ?? '').join(';'));
|
lines.push(header.map(key => row[key] ?? '').join(';'));
|
||||||
|
|||||||
@@ -158,10 +158,13 @@
|
|||||||
<th>wd.y</th>
|
<th>wd.y</th>
|
||||||
<th>wd.z</th>
|
<th>wd.z</th>
|
||||||
<th>СДИ</th>
|
<th>СДИ</th>
|
||||||
|
<th>СДИ лев.</th>
|
||||||
|
<th>СДИ прав.</th>
|
||||||
|
<th>ΔСДИ</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="angular-motion-result-body">
|
<tbody id="angular-motion-result-body">
|
||||||
<tr><td colspan="14" class="text-center text-muted py-4">Нет данных</td></tr>
|
<tr><td colspan="17" class="text-center text-muted py-4">Нет данных</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user