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
The distance in meters from the middle of the skydive to the outer bounding box for the initial view of a new rendered map.
Distance in meters back along the approach (upjump) direction from exit.
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.
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:
latitudelontigude
See
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
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