Работа с Orekit
Совсем недавно я обнаружил программу LicenseFinder. Она позволяет сканировать проект и найти все лицензии, которые используются в зависимостях. Это очень удобно, так как не все лицензии совместимы между собой. Я решил проверить свои open source проекты и обнаружил нестыковку лицензий для r2cloud. У меня использовалась библиотека predict4java с лицензией GPL-v2, а у моего проекта лицензия Apache 2.0. Такая зависимость фактически означает, что мой проект тоже должен распространяться под лицензией GPL-v2. И этого мне совсем не хотелось.
Ещё один недостаток predict4java - последний раз значительные изменения были в 2015 году. Да и сама библиотека является портом библиотеки Predict, которая в свою очередь является портом SGP4 модели, написанной на фортране.
Я начал искать альтернативы этой библиотеке. А надо сказать, что библиотека очень узкоспециализированная. Она рассчитывает положение спутника относительно Земли на основе модели SGP4. Заменить такую библиотеку это не то же самое, что поменять логирование.
Тем не менее я нашёл достаточно интересную альтернативу - OreKit. По заверениям разработчиков, эта библиотека используется в реальных космических миссиях для расчёта орбит. Ещё одним плюсом является то, что она написана на java. Идеально подходит для моего проекта.
Настройка
Прежде, чем работать с библиотекой, необходимо её настроить. Здесь всё говорит о том, что это серьёзная программа. Для конфигурации необходимо скачать orekit-data-master.zip. Этот файл содержит множество входных данных для различных математических моделей внутри Orekit. Например, модель Земли и leap second за всё время наблюдений.
После того как файл скачан и распакован в директорию, можно начать работать с библиотекой:
File orekitData = new File("./path/with/orekit-data-master/");
DataProvidersManager manager = DataProvidersManager.getInstance();
manager.addProvider(new DirectoryCrawler(orekitData))
Расчёт пролёта спутника
В моём проекте мне необходимо рассчитать время пролёта над станцией. Как только спутник показывается на горизонте, мне необходимо настроиться на его частоту и записать сигнал. Как только спутник уйдёт за горизонт, мне нужно остановить запись и начать её обрабатывать.
При этом есть два параметра:
- гарантированная высота над горизонтом. Спутник может пролетать достаточно далеко от наблюдателя и подниматься лишь на несколько градусов. Иногда этого недостаточно для приёма. Из-за этого необходимо найти все пролёты, при которых спутник поднимается на достаточную высоту.
- минимальная высота над горизонтом. Как только пролёт найден, необходимо найти начало и конец. Для этого есть минимальная высота.
Для начала необходимо описать параметры станции:
GeodeticPoint point = new GeodeticPoint(FastMath.toRadians(latitude), FastMath.toRadians(longitude), 0.0)
Frame earthFrame = FramesFactory.getITRF(IERSConventions.IERS_2010, true);
BodyShape earth = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, earthFrame);
TopocentricFrame baseStationFrame = new TopocentricFrame(earth, point, "station");
После этого создать Propagator
для задания орбиты спутника:
TLEPropagator tlePropagator = TLEPropagator.selectExtrapolator(new TLE("row 1", "row 2"));
С помощью него, можно узнать положение спутника в заданное время. Он может работать в 3-х режимах:
- slave. Приложение само вызывает расчёт координат
- master. Библиотека вызывает callback-функцию при расчёте
- ephemeral. Приложение вызывает расчёт координат. При этом время может идти в случайном порядке.
Более подробно о режимах можно узнать в официальной документации. Для расчёта необходимо использовать slave режим и ElevationExtremumDetector
. Это специальный фильтр, который будет отфильтровывать только те события, которые имеют максимум высоты.
ElevationExtremumDetector maxDetector = new ElevationExtremumDetector(60, 0.001, baseStationFrame).withMaxIter(48 * 60).withHandler(maxElevationHandler);
tlePropagator.clearEventsDetectors();
tlePropagator.addEventDetector(new EventSlopeFilter<EventDetector>(maxDetector, FilterType.TRIGGER_ONLY_DECREASING_EVENTS));
tlePropagator.setSlaveMode();
tlePropagator.propagate(initialDate, new AbsoluteDate(initialDate, 3600. * 24 * 2));
Здесь я создаю такой детектор, который будет рассчитывать высоту относительно baseStationFrame
и вызывать callback maxElevationHandler
. Расчёт будет идти с какой-то текущей даты initialDate
и на 2 дня вперёд.
Сам callback выглядит следующим образом:
@Override
public Action eventOccurred(SpacecraftState s, ElevationExtremumDetector detector, boolean increasing) {
if (FastMath.toDegrees(detector.getElevation(s)) > maxElevation) {
date = s.getDate();
return Action.STOP;
}
return Action.CONTINUE;
}
Если максимальная высота больше гарантированной, то запоминать время и останавливаться. В противном случае продолжать дальше искать.
После того как время с гарантированной высотой найдено, необходимо найти начало и конец пролёта. Для этого используется ElevationDetector
.
ElevationDetector boundsDetector = new ElevationDetector(60, 0.001, baseStationFrame).withConstantElevation(FastMath.toRadians(minElevation)).withHandler(minElevationHandler);
tlePropagator.clearEventsDetectors();
tlePropagator.addEventDetector(boundsDetector);
AbsoluteDate startDate = maxElevationHandler.getDate().shiftedBy(-20 * 60.0);
tlePropagator.propagate(startDate, startDate.shiftedBy(40 * 60.));
Этот детектор срабатывает, когда спутник поднимается выше минимальной высоты и опускается ниже минимальной.
Обработчик должен выглядеть следующим образом:
@Override
public Action eventOccurred(SpacecraftState s, ElevationDetector detector, boolean increasing) {
if (increasing) {
start = s.getDate();
return Action.CONTINUE;
}
end = s.getDate();
return Action.STOP;
}
Он должен запоминать время, если событие “восходящее”, то есть высота спутника увеличивается. И запоминать время, если событие “нисходящее”.
Расчёт допплеровского смещения сигнала
Ещё одна вещь, которая необходима в r2cloud - это расчёт смещения допплера для сигнала со спутника. Для этого необходимо рассчитать скорость сближения станции и спутника.
AbsoluteDate date = new AbsoluteDate(new Date(utcTimeMillis), TimeScalesFactory.getUTC());
PVCoordinates currentState = tlePropagator.getPVCoordinates(date);
final double rangeRate = currentLocation.getRangeRate(currentState, tlePropagator.getFrame(), date);
return (long) ((double) freq * (SPEED_OF_LIGHT - rangeRate) / SPEED_OF_LIGHT);
Здесь, для переданного времени utcTimeMillis
я высчитываю текущую позицию спутника. Далее нахожу скорость сближения двух тел. После того, как найдена скорость сближения, можно рассчитать частоту.
Вывод
OreKit - это очень мощная библиотека, с помощью которой можно смодулировать множество разных ситуаций. Правда, порог вхождения в неё достаточно высокий.