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
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.
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:
latitudelontigude
See
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
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