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

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.

Returns

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

See

st.pydeck_chart st.map