ssscoring.mapview

  1# See: https://github.com/pr3d4t0r/SSScoring/blob/master/LICENSE.txt
  2
  3from geopy import distance
  4from ssscoring.calc import jumpRunBearing
  5from ssscoring.constants import SAMPLE_RATE
  6from ssscoring.constants import SCORING_INTERVAL
  7from ssscoring.datatypes import JumpResults
  8from ssscoring.notebook import convertHexColorToRGB
  9
 10import pandas as pd
 11import pydeck as pdk
 12
 13
 14# *** constants ***
 15
 16DISTANCE_FROM_MIDDLE = 400.0
 17"""
 18The distance in meters from the middle of the skydive to the outer bounding box
 19for the initial view of a new rendered map.
 20"""
 21
 22JUMP_RUN_BACK_M = 100.0
 23"""
 24Distance in meters back along the approach (upjump) direction from exit.
 25"""
 26
 27JUMP_RUN_AHEAD_M = 750.0
 28"""
 29Distance in meters ahead along the jump run from exit — long arm so judges
 30can visually check whether the jumper stayed on jump run throughout the dive.
 31"""
 32
 33
 34# *** implementation ***
 35
 36def viewPointBox(data: pd.DataFrame) -> pd.DataFrame:
 37    """
 38    Calculate the NW and SE corners of a "box" delimiting the viewport area
 39    `DISTANCE_FROM_MIDDLE` meters away from the middle of the speed skydive.
 40
 41    Arguments
 42    ---------
 43        data
 44    A SSScoring dataframe with jump data.
 45
 46    Returns
 47    -------
 48    The NW and SE corners of the box, as terrestrial coordinates, in a dataframe
 49    with these columns:
 50
 51    - `latitude`
 52    - `lontigude`
 53
 54    See
 55    ---
 56    `ssscoring.calc.convertFlySight2SSScoring`
 57    """
 58    mid = len(data)//2
 59    datum = data.iloc[mid]
 60    origin = (datum.latitude, datum.longitude)
 61    pointNW = distance.distance(meters=DISTANCE_FROM_MIDDLE).destination(origin, bearing=315)
 62    pointSE = distance.distance(meters=DISTANCE_FROM_MIDDLE).destination(origin, bearing=135)
 63    data = list(zip([ pointNW[0], pointSE[0], ], [ pointNW[1], pointSE[1], ]))
 64    result = pd.DataFrame(data, columns=[ 'latitude', 'longitude', ])
 65    return result
 66
 67
 68def _resolveMaxScoreTimeFrom(jumpResult: JumpResults) -> float:
 69    scoreTime = jumpResult.scores[jumpResult.score]
 70    workData = jumpResult.data.reset_index(drop=True).copy()
 71    ref = workData.index[workData.plotTime == scoreTime][0]+round(SCORING_INTERVAL/SAMPLE_RATE/2.0)-1
 72    return workData.iloc[ref].plotTime
 73
 74
 75def _resolveMaxSpeedTimeFrom(jumpResult: JumpResults) -> float:
 76    rowIndex = jumpResult.data.vKMh.idxmax()
 77    plotTime = jumpResult.data.loc[rowIndex, 'plotTime']
 78    return plotTime
 79
 80
 81def speedJumpTrajectory(jumpResult: JumpResults,
 82                        displayScorePoint: bool=True) -> pdk.Deck:
 83    """
 84    Build the layers for a PyDeck map showing a jumper's trajectory.
 85
 86    Arguments
 87    ---------
 88        jumpResult
 89    A SSScoring `JumpResults` instance with the results of the jump.
 90
 91    Returns
 92    -------
 93    A PyDeck `deck` instance ready for rendering using PyDeck or Streamlit
 94    mapping facilities.
 95
 96    See
 97    ---
 98    `st.pydeck_chart`
 99    `st.map`
100    """
101    if jumpResult.data is not None and jumpResult.score != None and jumpResult.scores != None:
102        workData = jumpResult.data.copy()
103        scoresData = pd.DataFrame(list(jumpResult.scores.items()), columns=[ 'score', 'plotTime', ])
104        workData = pd.merge(workData, scoresData, on='plotTime', how='left')
105        workData.vKMh = workData.vKMh.apply(lambda x: round(x, 2))
106        workData.speedAngle = workData.speedAngle.apply(lambda x: round(x, 2))
107        if displayScorePoint:
108            maxValueTime = _resolveMaxScoreTimeFrom(jumpResult)
109            maxColorOuter = [ 0, 255, 0, ]
110            maxCollorDot = [ 0, 128, 0, ]
111        else:
112            maxValueTime = _resolveMaxSpeedTimeFrom(jumpResult)
113            maxColorOuter = [ 255, 0, 0, 255, ]  # red
114            maxCollorDot = [ 255, 255, 0, 255, ]  # yellow
115        bearing = jumpRunBearing(jumpResult.data)
116        exitRow = workData.iloc[0]
117        exitPoint = (exitRow.latitude, exitRow.longitude)
118        backPoint = distance.distance(meters=JUMP_RUN_BACK_M).destination(exitPoint, bearing=(bearing+180)%360)
119        aheadPoint = distance.distance(meters=JUMP_RUN_AHEAD_M).destination(exitPoint, bearing=bearing)
120        jumpRunPath = pd.DataFrame({
121            'path': [[[backPoint[1], backPoint[0]], [exitRow.longitude, exitRow.latitude], [aheadPoint[1], aheadPoint[0]]]],
122            'color': [[200, 200, 200, 180]],
123        })
124        layers = [
125            pdk.Layer(
126                'PathLayer',
127                data=jumpRunPath,
128                get_path='path',
129                get_color='color',
130                width_min_pixels=2,
131            ),
132            pdk.Layer(
133                'ScatterplotLayer',
134                data=workData.head(1),
135                get_color=[ 255, 126, 0, 255 ],
136                get_position=[ 'longitude', 'latitude', ],
137                get_radius=8),
138            pdk.Layer(
139                'ScatterplotLayer',
140                data=workData.tail(1),
141                get_color=[ 0, 192, 0, 160 ],
142                get_position=[ 'longitude', 'latitude', ],
143                get_radius=8),
144            pdk.Layer(
145                'ScatterplotLayer',
146                data=workData[workData.plotTime == maxValueTime],
147                get_color=maxColorOuter,
148                get_position=[ 'longitude', 'latitude', ],
149                get_radius=12),
150            pdk.Layer(
151                'ScatterplotLayer',
152                data=workData,
153                get_color=[ 0x64, 0x95, 0xed, 255 ],
154                get_position=[ 'longitude', 'latitude', ],
155                get_radius=2,
156                pickable=True),
157            pdk.Layer(
158                'ScatterplotLayer',
159                data=workData[workData.plotTime == maxValueTime],
160                get_color=maxCollorDot,
161                get_position=[ 'longitude', 'latitude', ],
162                get_radius=4),
163        ]
164        viewBox = viewPointBox(workData)
165        tooltip = {
166            # TODO:  Figure out how to plot the score @ plotTime here.
167            # 'html': '<b>plotTime:</b> {plotTime} s<br><b>Score:</b> {score} km/h<br><b>Speed:</b> {vKMh} km/h<br><b>speedAngle:</b> {speedAngle}º',
168            'html': '<b>plotTime:</b> {plotTime} s<br><b>Speed:</b> {vKMh} km/h<br><b>speedAngle:</b> {speedAngle}º',
169            'style': {
170                'backgroundColor': 'steelblue',
171                'color': 'white',
172            },
173            'cursor': 'default',
174        }
175        deck = pdk.Deck(
176            map_style = 'road',
177            layers=layers,
178            initial_view_state=pdk.data_utils.compute_view(viewBox[['longitude', 'latitude',]]),
179            tooltip=tooltip,
180        )
181        return deck
182
183
184def multipleSpeedJumpsTrajectories(jumpResults, tagColors: dict):
185    """
186    Build all the layers for a PyDeck map showing the trajectories of every jump
187    in the results set.
188
189    Arguments
190    ---------
191        jumpResults
192    A dictionary of all the jump results after processing.
193
194        tagColors
195    A tag→hex-color mapping produced by `resolveJumpColors`; fastest jump is
196    green, slowest red, others in blue shades.
197
198    Returns
199    -------
200    A PyDeck `deck` instance ready for rendering using PyDeck or Streamlit
201    mapping facilities.
202
203    See
204    ---
205    `st.pydeck_chart`
206    `st.map`
207    """
208    mapLayers = list()
209    resultTags = sorted(list(jumpResults.keys()), reverse=True)
210    for tag in resultTags:
211        result = jumpResults[tag]
212        if result.scores != None:
213            workData = result.data.copy()
214            exitPointData = workData.head(1)
215            exitPointData['label'] = tag
216            maxScoreTime = _resolveMaxScoreTimeFrom(result)
217            trackColor = convertHexColorToRGB(tagColors[tag])
218            layers = [
219                pdk.Layer(
220                    'ScatterplotLayer',
221                    data=exitPointData,
222                    get_color=[ 255, 126, 0, 255 ],
223                    get_position=[ 'longitude', 'latitude', ],
224                    pickable=True,
225                    get_radius=8),
226                pdk.Layer(
227                    'TextLayer',
228                    data=exitPointData,
229                    get_position=[ 'longitude', 'latitude', ],
230                    get_text='label',
231                    get_color=trackColor+[255],
232                    get_background_color=[ 0, 0, 0, 255, ],
233                    background=True,
234                    get_size=12,
235                ),
236                pdk.Layer(
237                    'ScatterplotLayer',
238                    data=workData.tail(1),
239                    get_color=[ 0, 192, 0, 160 ],
240                    get_position=[ 'longitude', 'latitude', ],
241                    get_radius=8),
242                pdk.Layer(
243                    'ScatterplotLayer',
244                    data=workData[workData.plotTime == maxScoreTime],
245                    get_color=[ 0, 255, 0, ],
246                    get_position=[ 'longitude', 'latitude', ],
247                    get_radius=12),
248                pdk.Layer(
249                    'ScatterplotLayer',
250                    data=workData,
251                    get_color=trackColor,
252                    get_position=[ 'longitude', 'latitude', ],
253                    get_radius=2),
254                pdk.Layer(
255                    'ScatterplotLayer',
256                    data=workData[workData.plotTime == maxScoreTime],
257                    get_color=[ 0, 128, 0, ],
258                    get_position=[ 'longitude', 'latitude', ],
259                    get_radius=4),
260            ]
261            mapLayers += layers
262    viewBox = viewPointBox(workData)
263    deck = pdk.Deck(
264        map_style = 'road',
265        initial_view_state=pdk.data_utils.compute_view(viewBox[['longitude', 'latitude',]]),
266        layers=mapLayers,
267    )
268    return deck
DISTANCE_FROM_MIDDLE = 400.0

The distance in meters from the middle of the skydive to the outer bounding box for the initial view of a new rendered map.

JUMP_RUN_BACK_M = 100.0

Distance in meters back along the approach (upjump) direction from exit.

JUMP_RUN_AHEAD_M = 750.0

Distance in meters ahead along the jump run from exit — long arm so judges can visually check whether the jumper stayed on jump run throughout the dive.

def viewPointBox(data: pandas.DataFrame) -> pandas.DataFrame:
37def viewPointBox(data: pd.DataFrame) -> pd.DataFrame:
38    """
39    Calculate the NW and SE corners of a "box" delimiting the viewport area
40    `DISTANCE_FROM_MIDDLE` meters away from the middle of the speed skydive.
41
42    Arguments
43    ---------
44        data
45    A SSScoring dataframe with jump data.
46
47    Returns
48    -------
49    The NW and SE corners of the box, as terrestrial coordinates, in a dataframe
50    with these columns:
51
52    - `latitude`
53    - `lontigude`
54
55    See
56    ---
57    `ssscoring.calc.convertFlySight2SSScoring`
58    """
59    mid = len(data)//2
60    datum = data.iloc[mid]
61    origin = (datum.latitude, datum.longitude)
62    pointNW = distance.distance(meters=DISTANCE_FROM_MIDDLE).destination(origin, bearing=315)
63    pointSE = distance.distance(meters=DISTANCE_FROM_MIDDLE).destination(origin, bearing=135)
64    data = list(zip([ pointNW[0], pointSE[0], ], [ pointNW[1], pointSE[1], ]))
65    result = pd.DataFrame(data, columns=[ 'latitude', 'longitude', ])
66    return result

Calculate the NW and SE corners of a "box" delimiting the viewport area DISTANCE_FROM_MIDDLE meters away from the middle of the speed skydive.

Arguments

data

A SSScoring dataframe with jump data.

Returns

The NW and SE corners of the box, as terrestrial coordinates, in a dataframe with these columns:

  • latitude
  • lontigude

See

ssscoring.calc.convertFlySight2SSScoring

def speedJumpTrajectory( jumpResult: ssscoring.datatypes.JumpResults, displayScorePoint: bool = True) -> pydeck.bindings.deck.Deck:
 82def speedJumpTrajectory(jumpResult: JumpResults,
 83                        displayScorePoint: bool=True) -> pdk.Deck:
 84    """
 85    Build the layers for a PyDeck map showing a jumper's trajectory.
 86
 87    Arguments
 88    ---------
 89        jumpResult
 90    A SSScoring `JumpResults` instance with the results of the jump.
 91
 92    Returns
 93    -------
 94    A PyDeck `deck` instance ready for rendering using PyDeck or Streamlit
 95    mapping facilities.
 96
 97    See
 98    ---
 99    `st.pydeck_chart`
100    `st.map`
101    """
102    if jumpResult.data is not None and jumpResult.score != None and jumpResult.scores != None:
103        workData = jumpResult.data.copy()
104        scoresData = pd.DataFrame(list(jumpResult.scores.items()), columns=[ 'score', 'plotTime', ])
105        workData = pd.merge(workData, scoresData, on='plotTime', how='left')
106        workData.vKMh = workData.vKMh.apply(lambda x: round(x, 2))
107        workData.speedAngle = workData.speedAngle.apply(lambda x: round(x, 2))
108        if displayScorePoint:
109            maxValueTime = _resolveMaxScoreTimeFrom(jumpResult)
110            maxColorOuter = [ 0, 255, 0, ]
111            maxCollorDot = [ 0, 128, 0, ]
112        else:
113            maxValueTime = _resolveMaxSpeedTimeFrom(jumpResult)
114            maxColorOuter = [ 255, 0, 0, 255, ]  # red
115            maxCollorDot = [ 255, 255, 0, 255, ]  # yellow
116        bearing = jumpRunBearing(jumpResult.data)
117        exitRow = workData.iloc[0]
118        exitPoint = (exitRow.latitude, exitRow.longitude)
119        backPoint = distance.distance(meters=JUMP_RUN_BACK_M).destination(exitPoint, bearing=(bearing+180)%360)
120        aheadPoint = distance.distance(meters=JUMP_RUN_AHEAD_M).destination(exitPoint, bearing=bearing)
121        jumpRunPath = pd.DataFrame({
122            'path': [[[backPoint[1], backPoint[0]], [exitRow.longitude, exitRow.latitude], [aheadPoint[1], aheadPoint[0]]]],
123            'color': [[200, 200, 200, 180]],
124        })
125        layers = [
126            pdk.Layer(
127                'PathLayer',
128                data=jumpRunPath,
129                get_path='path',
130                get_color='color',
131                width_min_pixels=2,
132            ),
133            pdk.Layer(
134                'ScatterplotLayer',
135                data=workData.head(1),
136                get_color=[ 255, 126, 0, 255 ],
137                get_position=[ 'longitude', 'latitude', ],
138                get_radius=8),
139            pdk.Layer(
140                'ScatterplotLayer',
141                data=workData.tail(1),
142                get_color=[ 0, 192, 0, 160 ],
143                get_position=[ 'longitude', 'latitude', ],
144                get_radius=8),
145            pdk.Layer(
146                'ScatterplotLayer',
147                data=workData[workData.plotTime == maxValueTime],
148                get_color=maxColorOuter,
149                get_position=[ 'longitude', 'latitude', ],
150                get_radius=12),
151            pdk.Layer(
152                'ScatterplotLayer',
153                data=workData,
154                get_color=[ 0x64, 0x95, 0xed, 255 ],
155                get_position=[ 'longitude', 'latitude', ],
156                get_radius=2,
157                pickable=True),
158            pdk.Layer(
159                'ScatterplotLayer',
160                data=workData[workData.plotTime == maxValueTime],
161                get_color=maxCollorDot,
162                get_position=[ 'longitude', 'latitude', ],
163                get_radius=4),
164        ]
165        viewBox = viewPointBox(workData)
166        tooltip = {
167            # TODO:  Figure out how to plot the score @ plotTime here.
168            # 'html': '<b>plotTime:</b> {plotTime} s<br><b>Score:</b> {score} km/h<br><b>Speed:</b> {vKMh} km/h<br><b>speedAngle:</b> {speedAngle}º',
169            'html': '<b>plotTime:</b> {plotTime} s<br><b>Speed:</b> {vKMh} km/h<br><b>speedAngle:</b> {speedAngle}º',
170            'style': {
171                'backgroundColor': 'steelblue',
172                'color': 'white',
173            },
174            'cursor': 'default',
175        }
176        deck = pdk.Deck(
177            map_style = 'road',
178            layers=layers,
179            initial_view_state=pdk.data_utils.compute_view(viewBox[['longitude', 'latitude',]]),
180            tooltip=tooltip,
181        )
182        return deck

Build the layers for a PyDeck map showing a jumper's trajectory.

Arguments

jumpResult

A SSScoring JumpResults instance with the results of the jump.

Returns

A PyDeck deck instance ready for rendering using PyDeck or Streamlit mapping facilities.

See

st.pydeck_chart st.map

def multipleSpeedJumpsTrajectories(jumpResults, tagColors: dict):
185def multipleSpeedJumpsTrajectories(jumpResults, tagColors: dict):
186    """
187    Build all the layers for a PyDeck map showing the trajectories of every jump
188    in the results set.
189
190    Arguments
191    ---------
192        jumpResults
193    A dictionary of all the jump results after processing.
194
195        tagColors
196    A tag→hex-color mapping produced by `resolveJumpColors`; fastest jump is
197    green, slowest red, others in blue shades.
198
199    Returns
200    -------
201    A PyDeck `deck` instance ready for rendering using PyDeck or Streamlit
202    mapping facilities.
203
204    See
205    ---
206    `st.pydeck_chart`
207    `st.map`
208    """
209    mapLayers = list()
210    resultTags = sorted(list(jumpResults.keys()), reverse=True)
211    for tag in resultTags:
212        result = jumpResults[tag]
213        if result.scores != None:
214            workData = result.data.copy()
215            exitPointData = workData.head(1)
216            exitPointData['label'] = tag
217            maxScoreTime = _resolveMaxScoreTimeFrom(result)
218            trackColor = convertHexColorToRGB(tagColors[tag])
219            layers = [
220                pdk.Layer(
221                    'ScatterplotLayer',
222                    data=exitPointData,
223                    get_color=[ 255, 126, 0, 255 ],
224                    get_position=[ 'longitude', 'latitude', ],
225                    pickable=True,
226                    get_radius=8),
227                pdk.Layer(
228                    'TextLayer',
229                    data=exitPointData,
230                    get_position=[ 'longitude', 'latitude', ],
231                    get_text='label',
232                    get_color=trackColor+[255],
233                    get_background_color=[ 0, 0, 0, 255, ],
234                    background=True,
235                    get_size=12,
236                ),
237                pdk.Layer(
238                    'ScatterplotLayer',
239                    data=workData.tail(1),
240                    get_color=[ 0, 192, 0, 160 ],
241                    get_position=[ 'longitude', 'latitude', ],
242                    get_radius=8),
243                pdk.Layer(
244                    'ScatterplotLayer',
245                    data=workData[workData.plotTime == maxScoreTime],
246                    get_color=[ 0, 255, 0, ],
247                    get_position=[ 'longitude', 'latitude', ],
248                    get_radius=12),
249                pdk.Layer(
250                    'ScatterplotLayer',
251                    data=workData,
252                    get_color=trackColor,
253                    get_position=[ 'longitude', 'latitude', ],
254                    get_radius=2),
255                pdk.Layer(
256                    'ScatterplotLayer',
257                    data=workData[workData.plotTime == maxScoreTime],
258                    get_color=[ 0, 128, 0, ],
259                    get_position=[ 'longitude', 'latitude', ],
260                    get_radius=4),
261            ]
262            mapLayers += layers
263    viewBox = viewPointBox(workData)
264    deck = pdk.Deck(
265        map_style = 'road',
266        initial_view_state=pdk.data_utils.compute_view(viewBox[['longitude', 'latitude',]]),
267        layers=mapLayers,
268    )
269    return deck

Build all the layers for a PyDeck map showing the trajectories of every jump in the results set.

Arguments

jumpResults

A dictionary of all the jump results after processing.

tagColors

A tag→hex-color mapping produced by resolveJumpColors; fastest jump is green, slowest red, others in blue shades.

Returns

A PyDeck deck instance ready for rendering using PyDeck or Streamlit mapping facilities.

See

st.pydeck_chart st.map