fix в задании азимута
This commit is contained in:
+1
-1
@@ -279,7 +279,7 @@ abstract class AbstractPuudCalculator(
|
|||||||
return orbBookToOrbMatrix() * absToOrbBookMatrix(kaAbs.r, kaAbs.v) * routeAbs
|
return orbBookToOrbMatrix() * absToOrbBookMatrix(kaAbs.r, kaAbs.v) * routeAbs
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ellipsoidNormalInGreenwich(b: Double, l: Double): Vector3D =
|
protected fun ellipsoidNormalInGreenwich(b: Double, l: Double): Vector3D =
|
||||||
Vector3D(cos(b) * cos(l), cos(b) * sin(l), sin(b)).normSafe()
|
Vector3D(cos(b) * cos(l), cos(b) * sin(l), sin(b)).normSafe()
|
||||||
|
|
||||||
private fun eastDirectionInGreenwich(l: Double): Vector3D =
|
private fun eastDirectionInGreenwich(l: Double): Vector3D =
|
||||||
|
|||||||
+86
-9
@@ -8,6 +8,7 @@ import ballistics.utils.math.Quaternion3D
|
|||||||
import ballistics.utils.math.Vector3D
|
import ballistics.utils.math.Vector3D
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.atan2
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
@@ -60,7 +61,13 @@ open class AzimuthPUUD(
|
|||||||
time = tn,
|
time = tn,
|
||||||
reverse = initialSdi < 0.0,
|
reverse = initialSdi < 0.0,
|
||||||
)
|
)
|
||||||
var liv = initialLiv
|
var liv = alignVisirQuaternionToRoute(
|
||||||
|
liv = initialLiv,
|
||||||
|
time = tn,
|
||||||
|
routeNormalGsk = routeNormalGsk,
|
||||||
|
rAbs = firstAbs.r,
|
||||||
|
reverse = initialSdi < 0.0,
|
||||||
|
)
|
||||||
|
|
||||||
val result = mutableListOf<AngularMotionPoint>()
|
val result = mutableListOf<AngularMotionPoint>()
|
||||||
var t = tn
|
var t = tn
|
||||||
@@ -70,9 +77,16 @@ open class AzimuthPUUD(
|
|||||||
while (elapsed <= duration + EPS) {
|
while (elapsed <= duration + EPS) {
|
||||||
val orbital = pointAt(t)
|
val orbital = pointAt(t)
|
||||||
val ask = astro.grinvToASK(orbital)
|
val ask = astro.grinvToASK(orbital)
|
||||||
val di = slantRangeFromQuaternion(liv, ask.r)
|
|
||||||
val sdiForTime = sdiAt(id.sdi, elapsed)
|
val sdiForTime = sdiAt(id.sdi, elapsed)
|
||||||
val omegaVisir = ownCornerSpeed(t, liv, di, ask.r, ask.v, sdiForTime)
|
liv = alignVisirQuaternionToRoute(
|
||||||
|
liv = liv,
|
||||||
|
time = t,
|
||||||
|
routeNormalGsk = routeNormalGsk,
|
||||||
|
rAbs = ask.r,
|
||||||
|
reverse = sdiForTime < 0.0,
|
||||||
|
)
|
||||||
|
val di = slantRangeFromQuaternion(liv, ask.r)
|
||||||
|
val omegaVisir = ownCornerSpeed(t, liv, di, ask.r, ask.v, sdiForTime, routeNormalGsk)
|
||||||
val orientation = orientationFromVisirQuaternion(orbital, liv)
|
val orientation = orientationFromVisirQuaternion(orbital, liv)
|
||||||
val point = buildIntegratedPoint(t, orientation, omegaVisir, previous, sickle)
|
val point = buildIntegratedPoint(t, orientation, omegaVisir, previous, sickle)
|
||||||
result += point
|
result += point
|
||||||
@@ -84,8 +98,17 @@ open class AzimuthPUUD(
|
|||||||
val pAsk = astro.grinvToASK(p)
|
val pAsk = astro.grinvToASK(p)
|
||||||
val pDi = slantRangeFromQuaternion(q, pAsk.r)
|
val pDi = slantRangeFromQuaternion(q, pAsk.r)
|
||||||
val pSdi = sdiAt(id.sdi, time - tn)
|
val pSdi = sdiAt(id.sdi, time - tn)
|
||||||
ownCornerSpeed(time, q, pDi, pAsk.r, pAsk.v, pSdi)
|
ownCornerSpeed(time, q, pDi, pAsk.r, pAsk.v, pSdi, routeNormalGsk)
|
||||||
}
|
}
|
||||||
|
val nextOrbital = pointAt(t + step)
|
||||||
|
val nextAsk = astro.grinvToASK(nextOrbital)
|
||||||
|
liv = alignVisirQuaternionToRoute(
|
||||||
|
liv = liv,
|
||||||
|
time = t + step,
|
||||||
|
routeNormalGsk = routeNormalGsk,
|
||||||
|
rAbs = nextAsk.r,
|
||||||
|
reverse = sdiAt(id.sdi, elapsed + step) < 0.0,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
elapsed += step
|
elapsed += step
|
||||||
t += step
|
t += step
|
||||||
@@ -101,6 +124,7 @@ open class AzimuthPUUD(
|
|||||||
rAbs: Vector3D,
|
rAbs: Vector3D,
|
||||||
vAbs: Vector3D,
|
vAbs: Vector3D,
|
||||||
sdi: Double,
|
sdi: Double,
|
||||||
|
routeNormalGsk: Vector3D?,
|
||||||
): Vector3D {
|
): Vector3D {
|
||||||
val omegaEarth = Vector3D(0.0, 0.0, astro.earth.wEarth)
|
val omegaEarth = Vector3D(0.0, 0.0, astro.earth.wEarth)
|
||||||
val relVelocityAbs = vAbs - omegaEarth.rem(rAbs)
|
val relVelocityAbs = vAbs - omegaEarth.rem(rAbs)
|
||||||
@@ -156,11 +180,10 @@ open class AzimuthPUUD(
|
|||||||
val routeNormalAbs = astro.grinvToASK(routeNormalGsk, time).normSafe()
|
val routeNormalAbs = astro.grinvToASK(routeNormalGsk, time).normSafe()
|
||||||
val routeDirectionAbs = astro.grinvToASK(routeDirectionGsk, time).normSafe()
|
val routeDirectionAbs = astro.grinvToASK(routeDirectionGsk, time).normSafe()
|
||||||
|
|
||||||
// Ось движения изображения в визирной СК должна быть связана с касательной
|
// Азимут задаёт именно направление движения точки визирования по поверхности
|
||||||
// центральной линии маршрута, а не с хордой двух земных радиус-векторов.
|
// Земли: 0° — на север, 90° — на восток. Поэтому ось сканирования визирной
|
||||||
// Проекция азимутальной касательной в фокальную плоскость соответствует
|
// СК строится по проекции азимутальной касательной на фокальную плоскость,
|
||||||
// критериям Wz/D = 0 и Wx/D = const: поперечная составляющая обнуляется,
|
// без смены знака. Иначе азимут 0° фактически превращается в 180°.
|
||||||
// а продольная ось направлена по маршруту.
|
|
||||||
var routeAxis = (routeDirectionAbs - line * (routeDirectionAbs * line)).normSafe()
|
var routeAxis = (routeDirectionAbs - line * (routeDirectionAbs * line)).normSafe()
|
||||||
if (routeAxis.module() < EPS) {
|
if (routeAxis.module() < EPS) {
|
||||||
routeAxis = routeNormalAbs.rem(line).normSafe()
|
routeAxis = routeNormalAbs.rem(line).normSafe()
|
||||||
@@ -182,6 +205,60 @@ open class AzimuthPUUD(
|
|||||||
return quaternionFromMatrix(m).inverse().normalized()
|
return quaternionFromMatrix(m).inverse().normalized()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Доворачивает визирную систему вокруг текущей ЦЛВ так, чтобы ее ось
|
||||||
|
* сканирования была направлена по заданному азимутальному маршруту на Земле.
|
||||||
|
*
|
||||||
|
* В старой реализации азимут участвовал только в начальной ориентации. После
|
||||||
|
* интегрирования кватерниона оси фокальной плоскости могли дрейфовать вокруг
|
||||||
|
* ЦЛВ, поэтому симметричные азимуты, например 20° и 340°, давали одинаковую
|
||||||
|
* программу. Здесь азимутальный маршрут используется на каждом шаге: в текущей
|
||||||
|
* точке визирования строится касательная к плоскости маршрута и визирная СК
|
||||||
|
* поворачивается вокруг ЦЛВ до совпадения с этой касательной.
|
||||||
|
*/
|
||||||
|
private fun alignVisirQuaternionToRoute(
|
||||||
|
liv: Quaternion3D,
|
||||||
|
time: Double,
|
||||||
|
routeNormalGsk: Vector3D,
|
||||||
|
rAbs: Vector3D,
|
||||||
|
reverse: Boolean,
|
||||||
|
): Quaternion3D {
|
||||||
|
val lineAbs = (liv * Vector3D(1.0, 0.0, 0.0)).normSafe()
|
||||||
|
if (lineAbs.module() < EPS) return liv
|
||||||
|
|
||||||
|
val di = slantRangeFromQuaternion(liv, rAbs)
|
||||||
|
if (di.module() < EPS) return liv
|
||||||
|
|
||||||
|
val groundAbs = rAbs + di
|
||||||
|
val groundGsk = astro.askToGrinvich(groundAbs, time)
|
||||||
|
val groundBlh = astro.earth.xyz2blh(groundGsk)
|
||||||
|
val normalGsk = ellipsoidNormalInGreenwich(groundBlh.lat, groundBlh.long)
|
||||||
|
// Для плоскости маршрута с нормалью N_route и текущей нормали к ОЗЭ U
|
||||||
|
// касательная в направлении заданного азимута равна U x N_route. Обратный
|
||||||
|
// порядок N_route x U разворачивает маршрут: 0° ведёт на юг, 180° — на север.
|
||||||
|
var desiredGsk = normalGsk.rem(routeNormalGsk).normSafe()
|
||||||
|
if (desiredGsk.module() < EPS) return liv
|
||||||
|
if (reverse) {
|
||||||
|
desiredGsk = desiredGsk * -1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
val desiredAbsRaw = astro.grinvToASK(desiredGsk, time).normSafe()
|
||||||
|
val desiredAbs = (desiredAbsRaw - lineAbs * (desiredAbsRaw * lineAbs)).normSafe()
|
||||||
|
if (desiredAbs.module() < EPS) return liv
|
||||||
|
|
||||||
|
val currentAxisRaw = (liv * Vector3D(0.0, 1.0, 0.0)).normSafe()
|
||||||
|
val currentAxis = (currentAxisRaw - lineAbs * (currentAxisRaw * lineAbs)).normSafe()
|
||||||
|
if (currentAxis.module() < EPS) return liv
|
||||||
|
|
||||||
|
val cosAngle = (currentAxis * desiredAbs).coerceIn(-1.0, 1.0)
|
||||||
|
val sinAngle = lineAbs * currentAxis.rem(desiredAbs)
|
||||||
|
val angle = atan2(sinAngle, cosAngle)
|
||||||
|
if (abs(angle) < 1.0e-13) return liv
|
||||||
|
|
||||||
|
val correction = Quaternion3D().also { it.makeQuat(angle, lineAbs) }
|
||||||
|
return (correction * liv).normalized()
|
||||||
|
}
|
||||||
|
|
||||||
protected fun slantRangeFromQuaternion(liv: Quaternion3D, rAbs: Vector3D): Vector3D {
|
protected fun slantRangeFromQuaternion(liv: Quaternion3D, rAbs: Vector3D): Vector3D {
|
||||||
val lineAbs = (liv * Vector3D(1.0, 0.0, 0.0)).normSafe()
|
val lineAbs = (liv * Vector3D(1.0, 0.0, 0.0)).normSafe()
|
||||||
val a = astro.earth.ekvRadius
|
val a = astro.earth.ekvRadius
|
||||||
|
|||||||
+20
-7
@@ -41,6 +41,7 @@ class SmoothSDIPUUD(
|
|||||||
rAbs: Vector3D,
|
rAbs: Vector3D,
|
||||||
vAbs: Vector3D,
|
vAbs: Vector3D,
|
||||||
sdi: Double,
|
sdi: Double,
|
||||||
|
routeNormalGsk: Vector3D?,
|
||||||
): Vector3D {
|
): Vector3D {
|
||||||
val omegaEarth = Vector3D(0.0, 0.0, astro.earth.wEarth)
|
val omegaEarth = Vector3D(0.0, 0.0, astro.earth.wEarth)
|
||||||
val relVelocityAbs = vAbs - omegaEarth.rem(rAbs)
|
val relVelocityAbs = vAbs - omegaEarth.rem(rAbs)
|
||||||
@@ -54,7 +55,7 @@ class SmoothSDIPUUD(
|
|||||||
val miv = liv.matrix().transpose()
|
val miv = liv.matrix().transpose()
|
||||||
val currentAbs = rAbs + di
|
val currentAbs = rAbs + di
|
||||||
val currentGsk = astro.askToGrinvich(currentAbs, time)
|
val currentGsk = astro.askToGrinvich(currentAbs, time)
|
||||||
val mih = horizontalGeodesicToAbs(currentGsk, time)
|
val mih = horizontalGeodesicToAbs(currentGsk, time, routeNormalGsk)
|
||||||
val chv = miv * mih.transpose()
|
val chv = miv * mih.transpose()
|
||||||
val perspective = visToOptic * chv
|
val perspective = visToOptic * chv
|
||||||
val denom = perspective.first.x.coerceAwayFromZero()
|
val denom = perspective.first.x.coerceAwayFromZero()
|
||||||
@@ -68,15 +69,27 @@ class SmoothSDIPUUD(
|
|||||||
return omegaInVisir
|
return omegaInVisir
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun horizontalGeodesicToAbs(pointGsk: Vector3D, time: Double): Matrix3D {
|
private fun horizontalGeodesicToAbs(
|
||||||
|
pointGsk: Vector3D,
|
||||||
|
time: Double,
|
||||||
|
routeNormalGsk: Vector3D?,
|
||||||
|
): Matrix3D {
|
||||||
val blh = astro.earth.xyz2blh(pointGsk)
|
val blh = astro.earth.xyz2blh(pointGsk)
|
||||||
val upGsk = pointGsk.normSafe()
|
val upGsk = ellipsoidNormalInGreenwich(blh.lat, blh.long)
|
||||||
val eastGsk = Vector3D(-kotlin.math.sin(blh.long), kotlin.math.cos(blh.long), 0.0).normSafe()
|
val alongGsk = routeNormalGsk
|
||||||
val northGsk = upGsk.rem(eastGsk).normSafe()
|
?.let { upGsk.rem(it) }
|
||||||
|
?.normSafe()
|
||||||
|
?.takeIf { it.module() >= EPS }
|
||||||
|
?: Vector3D(
|
||||||
|
-kotlin.math.sin(blh.lat) * kotlin.math.cos(blh.long),
|
||||||
|
-kotlin.math.sin(blh.lat) * kotlin.math.sin(blh.long),
|
||||||
|
kotlin.math.cos(blh.lat),
|
||||||
|
).normSafe()
|
||||||
|
val crossGsk = upGsk.rem(alongGsk).normSafe()
|
||||||
val gskToAbs = Matrix3D().also { it.makeOzMatrix(astro.si2000(time)) }
|
val gskToAbs = Matrix3D().also { it.makeOzMatrix(astro.si2000(time)) }
|
||||||
return Matrix3D(
|
return Matrix3D(
|
||||||
gskToAbs * northGsk,
|
gskToAbs * alongGsk,
|
||||||
gskToAbs * eastGsk,
|
gskToAbs * crossGsk,
|
||||||
gskToAbs * upGsk,
|
gskToAbs * upGsk,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-133
@@ -1,40 +1,31 @@
|
|||||||
package space.nstart.pcp.angularmotion
|
package space.nstart.pcp.angularmotion
|
||||||
|
|
||||||
import ballistics.mpl.OrientOnPointCalculator
|
import ballistics.flightLine.PointOnEarthCalculator
|
||||||
import ballistics.types.BLHPoint
|
|
||||||
import ballistics.types.EarthType
|
import ballistics.types.EarthType
|
||||||
import ballistics.types.OrbitalPoint
|
import ballistics.types.Orientation
|
||||||
import ballistics.types.TangageType
|
import ballistics.types.THBLPoint
|
||||||
import ballistics.types.WorkCSType
|
import ballistics.types.WorkCSType
|
||||||
import ballistics.utils.earth.getEarth
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.cos
|
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.sin
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Строит WKT-контур полосы съемки по результату ПУУД.
|
* Строит WKT-контур полосы съемки по результату ПУУД.
|
||||||
*
|
*
|
||||||
* [captureAngleDeg] трактуется как полуширина полосы: правая/левая граница соответствуют
|
* [captureAngleDeg] трактуется как полуширина полосы. Правая/левая граница строятся
|
||||||
* ориентациям на точки с креном `centerKren + capture` и `centerKren - capture`.
|
* прямым пересечением визирного луча с Землей через [PointOnEarthCalculator].
|
||||||
*/
|
*/
|
||||||
class SurveyContourCalculator(
|
class SurveyContourCalculator(
|
||||||
earthType: EarthType = EarthType.PZ90d02,
|
earthType: EarthType = EarthType.PZ90d02,
|
||||||
wcs: WorkCSType = WorkCSType.WCSOrbit,
|
wcs: WorkCSType = WorkCSType.WCSOrbit,
|
||||||
tangageType: TangageType = TangageType.TTProactive,
|
|
||||||
) {
|
) {
|
||||||
private val earth = getEarth(earthType)
|
private val pointOnEarthCalculator = PointOnEarthCalculator(earthType, wcs)
|
||||||
private val orientCalculator = OrientOnPointCalculator(earthType, wcs, tangageType)
|
|
||||||
|
|
||||||
fun calculate(result: AngularMotionResult, captureAngleDeg: Double): String {
|
fun calculate(result: AngularMotionResult, captureAngleDeg: Double): String {
|
||||||
require(captureAngleDeg > 0.0 && captureAngleDeg.isFinite()) {
|
require(captureAngleDeg > 0.0 && captureAngleDeg.isFinite()) {
|
||||||
"Угол захвата аппаратуры должен быть положительным"
|
"Угол захвата аппаратуры должен быть положительным"
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourcePoints = result.points.filter { it.groundPoint != null }
|
val sourcePoints = result.points
|
||||||
require(sourcePoints.size >= MIN_CONTOUR_POINTS) {
|
require(sourcePoints.size >= MIN_CONTOUR_POINTS) {
|
||||||
"Для построения контура съемки требуется не менее $MIN_CONTOUR_POINTS точек ПУУД"
|
"Для построения контура съемки требуется не менее $MIN_CONTOUR_POINTS точек ПУУД"
|
||||||
}
|
}
|
||||||
@@ -44,9 +35,22 @@ class SurveyContourCalculator(
|
|||||||
val left = ArrayList<SurveyContourPoint>(sourcePoints.size)
|
val left = ArrayList<SurveyContourPoint>(sourcePoints.size)
|
||||||
|
|
||||||
sourcePoints.forEach { point ->
|
sourcePoints.forEach { point ->
|
||||||
val centerKren = orientationKren(point.orbitalPoint, point.groundPoint!!)
|
val rightPoint = pointOnEarthCalculator.pointOnEarth(
|
||||||
right += boundaryPoint(point.orbitalPoint, point.groundPoint!!, centerKren + captureAngleRad)
|
point.orbitalPoint,
|
||||||
left += boundaryPoint(point.orbitalPoint, point.groundPoint!!, centerKren - captureAngleRad)
|
point.orientation.withKren(point.orientation.kren + captureAngleRad),
|
||||||
|
)
|
||||||
|
val leftPoint = pointOnEarthCalculator.pointOnEarth(
|
||||||
|
point.orbitalPoint,
|
||||||
|
point.orientation.withKren(point.orientation.kren - captureAngleRad),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (rightPoint != null && leftPoint != null) {
|
||||||
|
right += rightPoint.toContourPoint()
|
||||||
|
left += leftPoint.toContourPoint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(right.size >= MIN_CONTOUR_POINTS && left.size >= MIN_CONTOUR_POINTS) {
|
||||||
|
"Для построения контура съемки не удалось рассчитать границы полосы"
|
||||||
}
|
}
|
||||||
|
|
||||||
val shell = ArrayList<SurveyContourPoint>(right.size + left.size + 1)
|
val shell = ArrayList<SurveyContourPoint>(right.size + left.size + 1)
|
||||||
@@ -58,102 +62,6 @@ class SurveyContourCalculator(
|
|||||||
return toWkt(shell)
|
return toWkt(shell)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun boundaryPoint(orbitalPoint: OrbitalPoint, center: BLHPoint, targetKren: Double): SurveyContourPoint {
|
|
||||||
val centerKren = orientationKren(orbitalPoint, center)
|
|
||||||
val targetDelta = angleDelta(targetKren, centerKren)
|
|
||||||
if (abs(targetDelta) < EPS) {
|
|
||||||
return center.toContourPoint()
|
|
||||||
}
|
|
||||||
|
|
||||||
val direction = krenGradientDirection(orbitalPoint, center, centerKren, targetDelta)
|
|
||||||
var high = initialSearchDistanceMeters(orbitalPoint, center, centerKren, targetDelta, direction)
|
|
||||||
var highDelta = projectedKrenDelta(orbitalPoint, center, centerKren, direction, high)
|
|
||||||
|
|
||||||
while (sameDirection(targetDelta, highDelta) && abs(highDelta) < abs(targetDelta) && high < MAX_SEARCH_DISTANCE_M) {
|
|
||||||
high = (high * 2.0).coerceAtMost(MAX_SEARCH_DISTANCE_M)
|
|
||||||
highDelta = projectedKrenDelta(orbitalPoint, center, centerKren, direction, high)
|
|
||||||
}
|
|
||||||
|
|
||||||
var low = 0.0
|
|
||||||
repeat(BINARY_SEARCH_ITERATIONS) {
|
|
||||||
val mid = (low + high) / 2.0
|
|
||||||
val midDelta = projectedKrenDelta(orbitalPoint, center, centerKren, direction, mid)
|
|
||||||
if (sameDirection(targetDelta, midDelta) && abs(midDelta) < abs(targetDelta)) {
|
|
||||||
low = mid
|
|
||||||
} else {
|
|
||||||
high = mid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return shift(center, direction, high).toContourPoint()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun krenGradientDirection(
|
|
||||||
orbitalPoint: OrbitalPoint,
|
|
||||||
center: BLHPoint,
|
|
||||||
centerKren: Double,
|
|
||||||
targetDelta: Double,
|
|
||||||
): GroundDirection {
|
|
||||||
val eastKren = orientationKren(orbitalPoint, shift(center, GroundDirection(1.0, 0.0), GRADIENT_STEP_M))
|
|
||||||
val northKren = orientationKren(orbitalPoint, shift(center, GroundDirection(0.0, 1.0), GRADIENT_STEP_M))
|
|
||||||
val eastDelta = angleDelta(eastKren, centerKren)
|
|
||||||
val northDelta = angleDelta(northKren, centerKren)
|
|
||||||
val norm = sqrt(eastDelta * eastDelta + northDelta * northDelta)
|
|
||||||
val sign = if (targetDelta >= 0.0) 1.0 else -1.0
|
|
||||||
|
|
||||||
return if (norm < EPS) {
|
|
||||||
GroundDirection(sign, 0.0)
|
|
||||||
} else {
|
|
||||||
GroundDirection(sign * eastDelta / norm, sign * northDelta / norm)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initialSearchDistanceMeters(
|
|
||||||
orbitalPoint: OrbitalPoint,
|
|
||||||
center: BLHPoint,
|
|
||||||
centerKren: Double,
|
|
||||||
targetDelta: Double,
|
|
||||||
direction: GroundDirection,
|
|
||||||
): Double {
|
|
||||||
val deltaAtStep = abs(projectedKrenDelta(orbitalPoint, center, centerKren, direction, GRADIENT_STEP_M))
|
|
||||||
if (deltaAtStep < EPS) return DEFAULT_SEARCH_DISTANCE_M
|
|
||||||
return max(GRADIENT_STEP_M, GRADIENT_STEP_M * abs(targetDelta) / deltaAtStep * 1.25)
|
|
||||||
.coerceAtMost(MAX_SEARCH_DISTANCE_M)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun projectedKrenDelta(
|
|
||||||
orbitalPoint: OrbitalPoint,
|
|
||||||
center: BLHPoint,
|
|
||||||
centerKren: Double,
|
|
||||||
direction: GroundDirection,
|
|
||||||
distanceMeters: Double,
|
|
||||||
): Double = angleDelta(orientationKren(orbitalPoint, shift(center, direction, distanceMeters)), centerKren)
|
|
||||||
|
|
||||||
private fun orientationKren(orbitalPoint: OrbitalPoint, point: BLHPoint): Double =
|
|
||||||
orientCalculator.calculateOrientOnPoint(
|
|
||||||
orbitalPoint,
|
|
||||||
earth.blh2xyz(point.lat, point.long, point.h),
|
|
||||||
).kren
|
|
||||||
|
|
||||||
private fun shift(center: BLHPoint, direction: GroundDirection, distanceMeters: Double): BLHPoint {
|
|
||||||
val latitude = center.lat + direction.north * distanceMeters / earth.middleRadius
|
|
||||||
val parallelRadius = (earth.middleRadius * cos(center.lat)).coerceAwayFromZero()
|
|
||||||
val longitude = normalizeLongitude(center.long + direction.east * distanceMeters / parallelRadius)
|
|
||||||
return BLHPoint(clamp(latitude, -MAX_LATITUDE_RAD, MAX_LATITUDE_RAD), longitude, center.h)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sameDirection(expected: Double, actual: Double): Boolean =
|
|
||||||
actual == 0.0 || expected == 0.0 || expected * actual > 0.0
|
|
||||||
|
|
||||||
private fun angleDelta(value: Double, reference: Double): Double = normalizeAngle(value - reference)
|
|
||||||
|
|
||||||
private fun clamp(value: Double, min: Double, max: Double): Double =
|
|
||||||
when {
|
|
||||||
value < min -> min
|
|
||||||
value > max -> max
|
|
||||||
else -> value
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun toWkt(points: List<SurveyContourPoint>): String {
|
private fun toWkt(points: List<SurveyContourPoint>): String {
|
||||||
val coordinates = points.joinToString(", ") { point ->
|
val coordinates = points.joinToString(", ") { point ->
|
||||||
String.format(Locale.US, "%.8f %.8f", point.longitudeDeg, point.latitudeDeg)
|
String.format(Locale.US, "%.8f %.8f", point.longitudeDeg, point.latitudeDeg)
|
||||||
@@ -161,25 +69,15 @@ class SurveyContourCalculator(
|
|||||||
return "POLYGON (($coordinates))"
|
return "POLYGON (($coordinates))"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BLHPoint.toContourPoint(): SurveyContourPoint =
|
private fun Orientation.withKren(kren: Double): Orientation = Orientation(tang, kren, risk)
|
||||||
|
|
||||||
|
private fun THBLPoint.toContourPoint(): SurveyContourPoint =
|
||||||
SurveyContourPoint(latitudeDeg = lat.toDegrees(), longitudeDeg = long.toDegrees())
|
SurveyContourPoint(latitudeDeg = lat.toDegrees(), longitudeDeg = long.toDegrees())
|
||||||
|
|
||||||
private fun Double.toRadians(): Double = this * PI / 180.0
|
private fun Double.toRadians(): Double = this * PI / 180.0
|
||||||
|
|
||||||
private fun Double.toDegrees(): Double = this * 180.0 / PI
|
private fun Double.toDegrees(): Double = this * 180.0 / PI
|
||||||
|
|
||||||
private fun normalizeLongitude(value: Double): Double {
|
|
||||||
var longitude = value
|
|
||||||
while (longitude > PI) longitude -= 2.0 * PI
|
|
||||||
while (longitude < -PI) longitude += 2.0 * PI
|
|
||||||
return longitude
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class GroundDirection(
|
|
||||||
val east: Double,
|
|
||||||
val north: Double,
|
|
||||||
)
|
|
||||||
|
|
||||||
private data class SurveyContourPoint(
|
private data class SurveyContourPoint(
|
||||||
val latitudeDeg: Double,
|
val latitudeDeg: Double,
|
||||||
val longitudeDeg: Double,
|
val longitudeDeg: Double,
|
||||||
@@ -187,10 +85,5 @@ class SurveyContourCalculator(
|
|||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val MIN_CONTOUR_POINTS = 2
|
const val MIN_CONTOUR_POINTS = 2
|
||||||
const val GRADIENT_STEP_M = 1_000.0
|
|
||||||
const val DEFAULT_SEARCH_DISTANCE_M = 50_000.0
|
|
||||||
const val MAX_SEARCH_DISTANCE_M = 2_000_000.0
|
|
||||||
const val BINARY_SEARCH_ITERATIONS = 48
|
|
||||||
const val MAX_LATITUDE_RAD = PI / 2.0 - 1.0e-8
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -18,7 +18,6 @@ data class AngularMotionCalculationRequestDTO(
|
|||||||
val pointInCenter: Boolean = false,
|
val pointInCenter: Boolean = false,
|
||||||
val focusMm: Double? = null,
|
val focusMm: Double? = null,
|
||||||
val stepPuudSec: Double? = null,
|
val stepPuudSec: Double? = null,
|
||||||
val stepSdiSec: Double? = null,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class AngularMotionModeDTO {
|
enum class AngularMotionModeDTO {
|
||||||
|
|||||||
+1
-2
@@ -29,8 +29,7 @@ class AngularMotionCalculationRequestDTOJacksonTest {
|
|||||||
"azimuthDeg": 0,
|
"azimuthDeg": 0,
|
||||||
"pointInCenter": false,
|
"pointInCenter": false,
|
||||||
"focusMm": 5500,
|
"focusMm": 5500,
|
||||||
"stepPuudSec": 0.125,
|
"stepPuudSec": 0.125
|
||||||
"stepSdiSec": 20
|
|
||||||
}
|
}
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|||||||
-1
@@ -80,7 +80,6 @@ class AngularMotionService(
|
|||||||
val config = AngularMotionConfig(
|
val config = AngularMotionConfig(
|
||||||
focus = request.focusMm ?: defaultConfig.focus,
|
focus = request.focusMm ?: defaultConfig.focus,
|
||||||
stepPuud = request.stepPuudSec ?: defaultConfig.stepPuud,
|
stepPuud = request.stepPuudSec ?: defaultConfig.stepPuud,
|
||||||
stepSdi = request.stepSdiSec ?: defaultConfig.stepSdi,
|
|
||||||
)
|
)
|
||||||
val sourcePoints = loadOrbitalPoints(request, config)
|
val sourcePoints = loadOrbitalPoints(request, config)
|
||||||
val stepper = RungeStepper(sourcePoints.toMutableList(), EarthType.PZ90d02)
|
val stepper = RungeStepper(sourcePoints.toMutableList(), EarthType.PZ90d02)
|
||||||
|
|||||||
@@ -435,7 +435,6 @@
|
|||||||
pointInCenter: false,
|
pointInCenter: false,
|
||||||
focusMm: Number(el('angular-motion-focus').value || 5500),
|
focusMm: Number(el('angular-motion-focus').value || 5500),
|
||||||
stepPuudSec: Number(el('angular-motion-step').value || 0.125),
|
stepPuudSec: Number(el('angular-motion-step').value || 0.125),
|
||||||
stepSdiSec: Number(el('angular-motion-step-sdi').value || 20),
|
|
||||||
};
|
};
|
||||||
if (mode === 'AZIMUTH' || mode === 'SMOOTH_SDI') {
|
if (mode === 'AZIMUTH' || mode === 'SMOOTH_SDI') {
|
||||||
payload.sdi = Number(el('angular-motion-sdi').value);
|
payload.sdi = Number(el('angular-motion-sdi').value);
|
||||||
|
|||||||
@@ -111,10 +111,6 @@
|
|||||||
<label for="angular-motion-step" class="form-label">Шаг ПУУД, с</label>
|
<label for="angular-motion-step" class="form-label">Шаг ПУУД, с</label>
|
||||||
<input id="angular-motion-step" type="number" min="0.001" step="0.001" class="form-control" value="0.125">
|
<input id="angular-motion-step" type="number" min="0.001" step="0.001" class="form-control" value="0.125">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
|
||||||
<label for="angular-motion-step-sdi" class="form-label">Шаг СДИ, с</label>
|
|
||||||
<input id="angular-motion-step-sdi" type="number" min="0.001" step="0.001" class="form-control" value="20">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="angular-motion-submit" type="submit" class="btn btn-primary w-100">Рассчитать</button>
|
<button id="angular-motion-submit" type="submit" class="btn btn-primary w-100">Рассчитать</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user