package com.momega.spacesimulator.service; import java.util.HashMap; import java.util.Map; import org.apache.commons.math3.util.FastMath; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import com.momega.spacesimulator.model.Apsis; import com.momega.spacesimulator.model.ApsisType; import com.momega.spacesimulator.model.CelestialBody; import com.momega.spacesimulator.model.DefaultTimeInterval; import com.momega.spacesimulator.model.ExitSoiOrbitalPoint; import com.momega.spacesimulator.model.KeplerianElements; import com.momega.spacesimulator.model.Model; import com.momega.spacesimulator.model.MovingObject; import com.momega.spacesimulator.model.ReferenceFrame; import com.momega.spacesimulator.model.Spacecraft; import com.momega.spacesimulator.model.SphereOfInfluence; import com.momega.spacesimulator.model.TimeInterval; import com.momega.spacesimulator.model.Timestamp; import com.momega.spacesimulator.utils.TimeUtils; /** * The service containing all operation regarding sphere of influence (SOI) * Created by martin on 6/14/14. */ @Component public class SphereOfInfluenceService { @Autowired private KeplerianElementsService keplerianElementsService; @Autowired private ApsisService apsisService; private final static double EXIT_SOI_POINT_ERROR = Math.pow(10, 1); /** * The method find the sphere of influence of the given moving object (typically spacecraft) * @param model the model * @param movingObject the moving object * @param timestamp the given time frame (current timestamp or future one) * @return the instance of the sphere of influence */ public FindSoiResult findSoi(Model model, MovingObject movingObject, Timestamp timestamp) { return checkSoiOfPlanet(movingObject, timestamp, model.getRootSoi()); } protected FindSoiResult checkSoiOfPlanet(MovingObject movingObject, Timestamp timestamp, SphereOfInfluence parentSoi) { for(SphereOfInfluence soi : parentSoi.getChildren()) { FindSoiResult childSoi = checkSoiOfPlanet(movingObject, timestamp, soi); if (childSoi != null) { return childSoi; } } CelestialBody celestialBody = parentSoi.getBody(); double distance = movingObject.getPosition(timestamp).subtract(celestialBody.getPosition(timestamp)).length(); if (distance > parentSoi.getRadius()) { return null; } FindSoiResult result = new FindSoiResult(); result.setSphereOfInfluence(parentSoi); result.setDistance(distance); return result; } public CelestialBody findParentBody(Model model, CelestialBody celestialBody) { if (model.getSoiMap() == null) { model.setSoiMap(getAllChildren(model.getRootSoi())); } SphereOfInfluence soi = model.getSoiMap().get(celestialBody); Assert.notNull(soi); return soi.getParent().getBody(); } public synchronized void clear(Model model) { model.setSoiMap(null); } protected Map<CelestialBody, SphereOfInfluence> getAllChildren(SphereOfInfluence parentSoi) { Map<CelestialBody, SphereOfInfluence> result = new HashMap<>(); result.put(parentSoi.getBody(), parentSoi); for(SphereOfInfluence soi : parentSoi.getChildren()) { result.putAll(getAllChildren(soi)); } return result; } /** * Finds the point where spacecraft leaves the current sphere of influence. * The method set the point directly to the spacecraft instance * @param newTimestamp the current timestamp */ public void findExitSoi(Model model, Spacecraft spacecraft, Timestamp newTimestamp) { Assert.notNull(spacecraft); Assert.notNull(newTimestamp); ExitSoiOrbitalPoint exitSoiPoint = spacecraft.getExitSoiOrbitalPoint(); if (exitSoiPoint == null) { exitSoiPoint = new ExitSoiOrbitalPoint(); exitSoiPoint.setMovingObject(spacecraft); exitSoiPoint.setVisible(true); } double period = spacecraft.getKeplerianElements().getKeplerianOrbit().getPeriod(); TimeInterval interval = new DefaultTimeInterval(newTimestamp, newTimestamp.add(period)); boolean soiChangeFound = findExitSoi(model, spacecraft, exitSoiPoint, interval); if (!soiChangeFound) { spacecraft.setExitSoiOrbitalPoint(null); } else { spacecraft.setExitSoiOrbitalPoint(exitSoiPoint); CelestialBody targetBody = (CelestialBody) exitSoiPoint.getTargetObject(); Timestamp soiTimestamp = exitSoiPoint.getTimestamp(); KeplerianElements predictedKeplerianElements = keplerianElementsService.computeTargetKeplerianElements(spacecraft, targetBody, soiTimestamp); Apsis closestPoint = null; if (!targetBody.isStatic()) { Timestamp apsisTime = predictedKeplerianElements.timeToAngle(soiTimestamp, ApsisType.PERIAPSIS.getTrueAnomaly(), true); predictedKeplerianElements = keplerianElementsService.shiftTo(predictedKeplerianElements, apsisTime, targetBody); predictedKeplerianElements = keplerianElementsService.computeTargetKeplerianElements(predictedKeplerianElements, targetBody, apsisTime); closestPoint = exitSoiPoint.getClosestPoint(); if (closestPoint == null) { closestPoint = new Apsis(); closestPoint.setType(ApsisType.PERIAPSIS); closestPoint.setName(ApsisType.PERIAPSIS.getShortcut() + " of " + targetBody.getName()); closestPoint.setVisible(true); closestPoint.setMovingObject(spacecraft); } apsisService.computeApsis(closestPoint, predictedKeplerianElements, ApsisType.PERIAPSIS, apsisTime); } exitSoiPoint.setPredictedKeplerianElements(predictedKeplerianElements); exitSoiPoint.setClosestPoint(closestPoint); } } private boolean findExitSoi(Model model, Spacecraft spacecraft, ExitSoiOrbitalPoint exitSoiOrbitalPoint, TimeInterval interval) { double dT = TimeUtils.getDuration(interval) / 1000.0; boolean soiChangeFound = solveExitSoiPoint(model, spacecraft, exitSoiOrbitalPoint, interval, dT); if (soiChangeFound && (exitSoiOrbitalPoint.getError() > EXIT_SOI_POINT_ERROR) && (dT > 1)) { Timestamp start = exitSoiOrbitalPoint.getTimestamp().subtract(dT); Timestamp end = exitSoiOrbitalPoint.getTimestamp().add(dT); TimeInterval newInterval = new DefaultTimeInterval(start, end); return findExitSoi(model, spacecraft, exitSoiOrbitalPoint, newInterval); } return soiChangeFound; } private boolean solveExitSoiPoint(Model model, Spacecraft spacecraft, ExitSoiOrbitalPoint exitSoiPoint, TimeInterval interval, double dT) { Timestamp t = interval.getStartTime(); ReferenceFrame currentSoi = spacecraft.getKeplerianElements().getKeplerianOrbit().getReferenceFrame(); while(!t.after(interval.getEndTime())) { FindSoiResult findSoiResult = findSoi(model, spacecraft, t); if (!findSoiResult.getSphereOfInfluence().getBody().equals(currentSoi)) { CelestialBody newSoiBody = findSoiResult.getSphereOfInfluence().getBody(); double distance = findSoiResult.getDistance(); double error = FastMath.abs(findSoiResult.getSphereOfInfluence().getRadius() - distance); KeplerianElements spacecraftKe = KeplerianElements.fromTimestamp(spacecraft.getKeplerianElements().getKeplerianOrbit(), t); exitSoiPoint.setKeplerianElements(spacecraftKe); exitSoiPoint.setPosition(spacecraftKe.getCartesianPosition()); exitSoiPoint.setTimestamp(t); exitSoiPoint.setTargetObject(newSoiBody); exitSoiPoint.setError(error); exitSoiPoint.setName("Enter Soi " + newSoiBody.getName()); return true; } t = t.add(dT); } return false; } }