Skip to content

Commit 7d85a74

Browse files
Merge pull request #247 from RocketPy-Team/enh/dispersion_ellipses
ENH: haversine and exportElipses
2 parents 6aadeb3 + afcb6a5 commit 7d85a74

File tree

11 files changed

+987
-553
lines changed

11 files changed

+987
-553
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
attribute_class; parameter_name; mean_value; standard_deviation;
2+
environment; ensembleMember; [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];;
3+
motor; impulse; 1415.15; 35.3;
4+
motor; burnOut; 5.274; 1;
5+
motor; nozzleRadius; 0.021642; 0.0005;
6+
motor; throatRadius; 0.008; 0.0005;
7+
motor; grainSeparation; 0.006; 0.001;
8+
motor; grainDensity; 1707; 50;
9+
motor; grainOuterRadius; 0.0214; 0.2;
10+
motor; grainInitialInnerRadius; .0097;0.000375;
11+
motor; grainInitialHeight; 0.12; 0.001
12+
rocket; rocketMass; 8.257; 0.001;
13+
rocket; inertiaI; 3.675; 0.03675;
14+
rocket; inertiaZ; 0.007; 0.00007;
15+
rocket; radius; 0.04045; 0.001;
16+
rocket; distanceRocketNozzle; -1.024; .001;
17+
rocket; distanceRocketPropellant; -0.51; 0.001;
18+
rocket; powerOffDrag; 0.864857143; 0.03;
19+
rocket; powerOnDrag; 0.864857143; 0.03;
20+
rocket; noseLength; 0.274; 0.001;
21+
rocket; noseDistanceToCM; 1.134; 0.001
22+
fins; finSpan; 0.077; 0.0005;
23+
fins; finRootChord; 0.058; 0.0005;
24+
fins; finTipChord; 0.018; 0.0005;
25+
fins; finDistanceToCM; -0.906; 0.001;
26+
parachute; CdSDrogue; 0.4537; 0.07;
27+
parachute; lag_rec; 1; 0.5;
28+
parachute; lag_se; 0.73; 0.16;
29+
flight; inclination; 84.7; 1;
30+
flight; heading; 53; 2;
31+
flight; railLength; 5.7; 0.0005;

docs/notebooks/topography_usage.ipynb

Lines changed: 189 additions & 189 deletions
Large diffs are not rendered by default.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ requests
88
pytz
99
timezonefinder
1010
simplekml
11+
imageio

rocketpy/Dispersion.py

Lines changed: 210 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
# -*- coding: utf-8 -*-
22

3-
__author__ = "Mateus Stano Junqueira, Sofia Lopes Suesdek Rocha"
3+
__author__ = "Mateus Stano Junqueira, Sofia Lopes Suesdek Rocha, Abdulklech Sorban"
44
__copyright__ = "Copyright 20XX, Projeto Jupiter"
55
__license__ = "MIT"
66

77

8+
import math
89
import traceback
9-
from time import process_time, time
1010
import warnings
11+
from time import process_time, time
1112

1213
import matplotlib.pyplot as plt
1314
import numpy as np
15+
import simplekml
1416
from imageio import imread
1517
from IPython.display import display
1618
from matplotlib.patches import Ellipse
1719
from numpy.random import *
18-
from datetime import datetime
1920

2021
from .Environment import Environment
2122
from .Flight import Flight
2223
from .Function import Function
2324
from .Motor import HybridMotor, SolidMotor
2425
from .Rocket import Rocket
26+
from .utilities import invertedHaversine
2527

2628

2729
class Dispersion:
@@ -1203,10 +1205,15 @@ def plotDrogueFullyVelocity(self, dispersion_results):
12031205

12041206
return None
12051207

1206-
def plotEllipses(self, dispersion_results, image, realLandingPoint):
1208+
def createEllipses(self, dispersion_results):
1209+
"""A function to create apogee and impact ellipses from the dispersion
1210+
results.
12071211
1208-
# Import background map
1209-
img = imread(image)
1212+
Parameters
1213+
----------
1214+
dispersion_results : dict
1215+
A dictionary containing the results of the dispersion analysis.
1216+
"""
12101217

12111218
# Retrieve dispersion data por apogee and impact XY position
12121219
apogeeX = np.array(dispersion_results["apogeeX"])
@@ -1220,10 +1227,6 @@ def eigsorted(cov):
12201227
order = vals.argsort()[::-1]
12211228
return vals[order], vecs[:, order]
12221229

1223-
# Create plot figure
1224-
plt.figure(num=None, figsize=(8, 6), dpi=150, facecolor="w", edgecolor="k")
1225-
ax = plt.subplot(111)
1226-
12271230
# Calculate error ellipses for impact
12281231
impactCov = np.cov(impactX, impactY)
12291232
impactVals, impactVecs = eigsorted(impactCov)
@@ -1242,14 +1245,14 @@ def eigsorted(cov):
12421245
)
12431246
impactEll.set_facecolor((0, 0, 1, 0.2))
12441247
impact_ellipses.append(impactEll)
1245-
ax.add_artist(impactEll)
12461248

12471249
# Calculate error ellipses for apogee
12481250
apogeeCov = np.cov(apogeeX, apogeeY)
12491251
apogeeVals, apogeeVecs = eigsorted(apogeeCov)
12501252
apogeeTheta = np.degrees(np.arctan2(*apogeeVecs[:, 0][::-1]))
12511253
apogeeW, apogeeH = 2 * np.sqrt(apogeeVals)
12521254

1255+
apogee_ellipses = []
12531256
# Draw error ellipses for apogee
12541257
for j in [1, 2, 3]:
12551258
apogeeEll = Ellipse(
@@ -1260,7 +1263,51 @@ def eigsorted(cov):
12601263
color="black",
12611264
)
12621265
apogeeEll.set_facecolor((0, 1, 0, 0.2))
1263-
ax.add_artist(apogeeEll)
1266+
apogee_ellipses.append(apogeeEll)
1267+
return impact_ellipses, apogee_ellipses
1268+
1269+
def plotEllipses(
1270+
self,
1271+
dispersion_results,
1272+
image=None,
1273+
realLandingPoint=None,
1274+
perimeterSize=3000,
1275+
xlim=(-3000, 3000),
1276+
ylim=(-3000, 3000),
1277+
):
1278+
"""A function to plot the error ellipses for the apogee and impact
1279+
points of the rocket. The function also plots the real landing point, if
1280+
given
1281+
1282+
Parameters
1283+
----------
1284+
dispersion_results : dict
1285+
A dictionary containing the results of the dispersion analysis
1286+
image : str
1287+
The path to the image to be used as the background
1288+
realLandingPoint : tuple, optional
1289+
A tuple containing the real landing point of the rocket, by default None
1290+
"""
1291+
# Import background map
1292+
if image is not None:
1293+
img = imread(image)
1294+
1295+
# Retrieve dispersion data por apogee and impact XY position
1296+
apogeeX = np.array(dispersion_results["apogeeX"])
1297+
apogeeY = np.array(dispersion_results["apogeeY"])
1298+
impactX = np.array(dispersion_results["impactX"])
1299+
impactY = np.array(dispersion_results["impactY"])
1300+
1301+
impact_ellipses, apogee_ellipses = self.createEllipses(dispersion_results)
1302+
1303+
# Create plot figure
1304+
plt.figure(num=None, figsize=(8, 6), dpi=150, facecolor="w", edgecolor="k")
1305+
ax = plt.subplot(111)
1306+
1307+
for ell in impact_ellipses:
1308+
ax.add_artist(ell)
1309+
for ell in apogee_ellipses:
1310+
ax.add_artist(ell)
12641311

12651312
# Draw launch point
12661313
plt.scatter(0, 0, s=30, marker="*", color="black", label="Launch Point")
@@ -1301,16 +1348,164 @@ def eigsorted(cov):
13011348
# You can translate the basemap by changing dx and dy (in meters)
13021349
dx = 0
13031350
dy = 0
1304-
plt.imshow(img, zorder=0, extent=[-3000 - dx, 3000 - dx, -3000 - dy, 3000 - dy])
1351+
if image is not None:
1352+
plt.imshow(
1353+
img,
1354+
zorder=0,
1355+
extent=[
1356+
-perimeterSize - dx,
1357+
perimeterSize - dx,
1358+
-perimeterSize - dy,
1359+
perimeterSize - dy,
1360+
],
1361+
)
13051362
plt.axhline(0, color="black", linewidth=0.5)
13061363
plt.axvline(0, color="black", linewidth=0.5)
1307-
plt.xlim(-3000, 3000)
1308-
plt.ylim(-3000, 3000)
1364+
plt.xlim(*xlim)
1365+
plt.ylim(*ylim)
13091366

13101367
# Save plot and show result
13111368
plt.savefig(str(self.filename) + ".pdf", bbox_inches="tight", pad_inches=0)
13121369
plt.savefig(str(self.filename) + ".svg", bbox_inches="tight", pad_inches=0)
13131370
plt.show()
1371+
return None
1372+
1373+
def prepareEllipses(self, ellipses, origin_lat, origin_lon, resolution=100):
1374+
"""Generate a list of latitude and longitude points for each ellipse in
1375+
ellipses.
1376+
1377+
Parameters
1378+
----------
1379+
ellipses : list
1380+
List of matplotlib.patches.Ellipse objects.
1381+
origin_lat : float
1382+
Latitude of the origin of the coordinate system.
1383+
origin_lon : float
1384+
Longitude of the origin of the coordinate system.
1385+
resolution : int, optional
1386+
Number of points to generate for each ellipse, by default 100
1387+
"""
1388+
outputs = []
1389+
1390+
for ell in ellipses:
1391+
# Get ellipse path points
1392+
center = ell.get_center()
1393+
width = ell.get_width()
1394+
height = ell.get_height()
1395+
angle = np.deg2rad(ell.get_angle())
1396+
points = []
1397+
1398+
for i in range(resolution):
1399+
x = width / 2 * math.cos(2 * np.pi * i / resolution)
1400+
y = height / 2 * math.sin(2 * np.pi * i / resolution)
1401+
x_rot = center[0] + x * math.cos(angle) - y * math.sin(angle)
1402+
y_rot = center[1] + x * math.sin(angle) + y * math.cos(angle)
1403+
points.append((x_rot, y_rot))
1404+
points = np.array(points)
1405+
1406+
# Convert path points to lat/lon
1407+
lat_lon_points = []
1408+
for point in points:
1409+
x = point[0]
1410+
y = point[1]
1411+
1412+
# Convert to distance and bearing
1413+
d = math.sqrt((x**2 + y**2))
1414+
bearing = math.atan2(
1415+
x, y
1416+
) # math.atan2 returns the angle in the range [-pi, pi]
1417+
1418+
lat_lon_points.append(
1419+
invertedHaversine(
1420+
origin_lat, origin_lon, d, bearing, eRadius=6.3781e6
1421+
)
1422+
)
1423+
1424+
# Export string
1425+
outputs.append(lat_lon_points)
1426+
return outputs
1427+
1428+
def exportEllipsesToKML(
1429+
self,
1430+
dispersion_results,
1431+
filename,
1432+
origin_lat,
1433+
origin_lon,
1434+
type="all",
1435+
resolution=100,
1436+
color="ff0000ff",
1437+
):
1438+
"""Generates a KML file with the ellipses on the impact point.
1439+
Parameters
1440+
----------
1441+
dispersion_results : dict
1442+
Contains dispersion results from the Monte Carlo simulation.
1443+
filename : String
1444+
Name to the KML exported file.
1445+
origin_lat : float
1446+
Latitude coordinate of Ellipses' geometric center, in degrees.
1447+
origin_lon : float
1448+
Latitude coordinate of Ellipses' geometric center, in degrees.
1449+
type : String
1450+
Type of ellipses to be exported. Options are: 'all', 'impact' and
1451+
'apogee'. Default is 'all', it exports both apogee and impact
1452+
ellipses.
1453+
resolution : int
1454+
Number of points to be used to draw the ellipse. Default is 100.
1455+
color : String
1456+
Color of the ellipse. Default is 'ff0000ff', which is red.
1457+
"""
1458+
1459+
impact_ellipses, apogee_ellipses = self.createEllipses(dispersion_results)
1460+
outputs = []
1461+
1462+
if type == "all" or type == "impact":
1463+
outputs = outputs + self.prepareEllipses(
1464+
impact_ellipses, origin_lat, origin_lon, resolution=resolution
1465+
)
1466+
1467+
if type == "all" or type == "apogee":
1468+
outputs = outputs + self.prepareEllipses(
1469+
apogee_ellipses, origin_lat, origin_lon, resolution=resolution
1470+
)
1471+
1472+
# Prepare data to KML file
1473+
kml_data = []
1474+
for i in range(len(outputs)):
1475+
temp = []
1476+
for j in range(len(outputs[i])):
1477+
temp.append((outputs[i][j][1], outputs[i][j][0])) # log, lat
1478+
kml_data.append(temp)
1479+
1480+
# Export to KML
1481+
kml = simplekml.Kml()
1482+
1483+
for i in range(len(outputs)):
1484+
if (type == "all" and i < 3) or (type == "impact"):
1485+
ellName = "Impact σ" + str(i + 1)
1486+
elif type == "all" and i >= 3:
1487+
ellName = "Apogee σ" + str(i - 2)
1488+
else:
1489+
ellName = "Apogee σ" + str(i + 1)
1490+
1491+
mult_ell = kml.newmultigeometry(name=ellName)
1492+
mult_ell.newpolygon(
1493+
outerboundaryis=kml_data[i],
1494+
name="Ellipse " + str(i),
1495+
)
1496+
# Setting ellipse style
1497+
mult_ell.tessellate = 1
1498+
mult_ell.visibility = 1
1499+
# mult_ell.innerboundaryis = kml_data
1500+
# mult_ell.outerboundaryis = kml_data
1501+
mult_ell.style.linestyle.color = color
1502+
mult_ell.style.linestyle.width = 3
1503+
mult_ell.style.polystyle.color = simplekml.Color.changealphaint(
1504+
100, simplekml.Color.blue
1505+
)
1506+
1507+
kml.save(filename)
1508+
return None
13141509

13151510
def meanLateralWindSpeed(self, dispersion_results):
13161511
print(

0 commit comments

Comments
 (0)