@@ -27,6 +27,7 @@ def __init__(self, mpstate):
2727 self .wp_change_time = 0
2828 self .fence_change_time = 0
2929 self .rally_change_time = 0
30+ self .terrain_contour_ids = []
3031 self .have_simstate = False
3132 self .have_vehicle = {}
3233 self .move_wp = - 1
@@ -65,8 +66,12 @@ def __init__(self, mpstate):
6566 ('showdirection' , bool , False ),
6667 ('setpos_accuracy' , float , 50 ),
6768 ('mission_color' , str , "white" ),
68- ('font_size' , float , 0.5 ) ])
69-
69+ ('font_size' , float , 0.5 ),
70+ ('contour_levels' , int , 20 ),
71+ ('contour_grid_spacing' , float , 30.0 ),
72+ ('contour_grid_extent' , float , 20000.0 ),
73+ ])
74+
7075 service = 'MicrosoftHyb'
7176 if 'MAP_SERVICE' in os .environ :
7277 service = os .environ ['MAP_SERVICE' ]
@@ -141,6 +146,12 @@ def __init__(self, mpstate):
141146 mavutil .mavlink .MAV_CMD_NAV_VTOL_LAND : "VL" ,
142147 }
143148
149+ self .add_menu (MPMenuSubMenu ('Terrain' , items = [
150+ MPMenuItem ('Show Contours' , returnkey = 'showTerrainContours' ),
151+ MPMenuItem ('Hide Contours' , returnkey = 'hideTerrainContours' ),
152+ MPMenuItem ('Remove Contours' , returnkey = 'removeTerrainContours' ),
153+ ]))
154+
144155 def add_menu (self , menu ):
145156 '''add to the default popup menu'''
146157 from MAVProxy .modules .mavproxy_map import mp_slipmap
@@ -192,7 +203,6 @@ def print_google_maps_link(self):
192203 pos = self .mpstate .click_location
193204 print ("https://www.google.com/maps/search/?api=1&query=%f,%f" % (pos [0 ], pos [1 ]))
194205
195-
196206 def write_JSON (self , fname , template , vardict ):
197207 '''write a JSON file in log directory'''
198208 fname = os .path .join (self .logdir , fname )
@@ -268,8 +278,6 @@ def cmd_map_marker(self, args, latlon=None):
268278
269279 print ("Wrote marker %s" % fname )
270280
271-
272-
273281 def cmd_map (self , args ):
274282 '''map commands'''
275283 from MAVProxy .modules .mavproxy_map import mp_slipmap
@@ -650,7 +658,6 @@ def display_fence(self):
650658 else :
651659 self .map .remove_object ('Fence' )
652660
653-
654661 def closest_waypoint (self , latlon ):
655662 '''find closest waypoint to a position'''
656663 (lat , lon ) = latlon
@@ -778,6 +785,12 @@ def handle_menu_event(self, obj):
778785 self .print_google_maps_link ()
779786 elif menuitem .returnkey == 'setServiceTerrain' :
780787 self .module ('terrain' ).cmd_terrain (['set' , 'source' , menuitem .get_choice ()])
788+ elif menuitem .returnkey == 'showTerrainContours' :
789+ self .display_terrain_contours ()
790+ elif menuitem .returnkey == 'hideTerrainContours' :
791+ self .hide_terrain_contours ()
792+ elif menuitem .returnkey == 'removeTerrainContours' :
793+ self .remove_terrain_contours ()
781794
782795 def map_callback (self , obj ):
783796 '''called when an event happens on the slipmap'''
@@ -1323,6 +1336,134 @@ def check_redisplay_rallypoints(self):
13231336 points .append ((nearest_land_wp .x , nearest_land_wp .y ))
13241337 self .map .add_object (mp_slipmap .SlipPolygon ('Rally Land %u' % (i + 1 ), points , 'RallyPoints' , (255 ,255 ,0 ), 2 ))
13251338
1339+ def display_terrain_contours (self ):
1340+ """
1341+ Show terrain contours
1342+ """
1343+ from MAVProxy .modules .mavproxy_map import mp_slipmap
1344+ import numpy as np
1345+
1346+ # configure matplotlib for non-gui use
1347+ import matplotlib
1348+ matplotlib .use ('Agg' )
1349+
1350+ # disable interactive plotting mode
1351+ import matplotlib .pyplot as plt
1352+ plt .ioff ()
1353+
1354+ terrain_module = self .module ('terrain' )
1355+ if terrain_module is None :
1356+ return
1357+
1358+ elevation_model = terrain_module .ElevationModel
1359+
1360+ # show contours if they have already been calculated
1361+ if len (self .terrain_contour_ids ) > 0 :
1362+ self .show_terrain_contours ()
1363+ return
1364+
1365+ # centre terrain grid about clicked location
1366+ if self .mpstate .click_location is None :
1367+ return
1368+
1369+ (lat , lon ) = self .mpstate .click_location
1370+
1371+ # retrieve grid options from map settings
1372+ grid_spacing = self .map_settings .contour_grid_spacing
1373+ grid_extent = self .map_settings .contour_grid_extent
1374+ levels = self .map_settings .contour_levels
1375+
1376+ # create mesh grid
1377+ x = np .arange (- 0.5 * grid_extent , 0.5 * grid_extent , grid_spacing )
1378+ y = np .arange (- 0.5 * grid_extent , 0.5 * grid_extent , grid_spacing )
1379+ x_grid , y_grid = np .meshgrid (x , y )
1380+
1381+ def terrain_surface (lat , lon , x , y ):
1382+ """
1383+ Calculate terrain altitudes for the NED offsets (x, y)
1384+ centred on (lat, lon).
1385+ """
1386+ alt = []
1387+ for east in y :
1388+ alt_y = []
1389+ for north in x :
1390+ (lat2 , lon2 ) = mp_util .gps_offset (lat , lon , east , north )
1391+ alt_y .append (elevation_model .GetElevation (lat2 , lon2 ))
1392+ alt .append (alt_y )
1393+ return alt
1394+
1395+ def ned_to_latlon (contours , lat , lon ):
1396+ """
1397+ Convert contour polygons in NED coordinates offset from (lat, lon)
1398+ to polygons in orthometric coordinates.
1399+ """
1400+ contours_latlon = []
1401+ for polygons in contours :
1402+ polygons_latlon = []
1403+ for polygon in polygons :
1404+ polygon_latlon = []
1405+ for point in polygon :
1406+ (north , east ) = point
1407+ (lat2 , lon2 ) = mp_util .gps_offset (lat , lon , east , north )
1408+ polygon_latlon .append ([lat2 , lon2 ])
1409+ polygons_latlon .append (polygon_latlon )
1410+ contours_latlon .append (polygons_latlon )
1411+ return contours_latlon
1412+
1413+ # generate surface and contours
1414+ z_grid = np .array (terrain_surface (lat , lon , x , y ))
1415+ _ , (ax1 ) = plt .subplots (1 , 1 , figsize = (10 ,10 ))
1416+ cs = ax1 .contour (x_grid , y_grid , z_grid , levels = levels )
1417+ contours = ned_to_latlon (cs .allsegs , lat , lon )
1418+
1419+ # add terrain layer and contour polygons
1420+ self .map .add_object (mp_slipmap .SlipClearLayer ('Terrain' ))
1421+
1422+ self .terrain_contour_ids .clear ()
1423+ num_contours = len (contours )
1424+ for i in range (num_contours ):
1425+ polygons = contours [i ]
1426+ for j in range (len (polygons )):
1427+ p = polygons [j ]
1428+ if len (p ) > 1 :
1429+ id = f"terrain { i } { j } "
1430+ self .terrain_contour_ids .append (id )
1431+ contour_colour = (255 , 255 , 255 )
1432+ self .map .add_object (mp_slipmap .SlipPolygon (
1433+ id , p ,
1434+ layer = 'Terrain' , linewidth = 1 ,
1435+ colour = contour_colour ,
1436+ showcircles = False
1437+ ))
1438+
1439+ def show_terrain_contours (self ):
1440+ """
1441+ Show terrain contours.
1442+ """
1443+ # unhide polygons
1444+ for id in self .terrain_contour_ids :
1445+ self .map .hide_object (id , hide = False )
1446+
1447+ def hide_terrain_contours (self ):
1448+ """
1449+ Hide terrain contours.
1450+ """
1451+ # hide polygons
1452+ for id in self .terrain_contour_ids :
1453+ self .map .hide_object (id , hide = True )
1454+
1455+ def remove_terrain_contours (self ):
1456+ """
1457+ Remove terrain contours and the terrain clear layer.
1458+ """
1459+ # remove polygons
1460+ for id in self .terrain_contour_ids :
1461+ self .map .remove_object (id )
1462+ # remove layer
1463+ self .map .remove_object ('Terrain' )
1464+ self .terrain_contour_ids .clear ()
1465+
1466+
13261467def init (mpstate ):
13271468 '''initialise module'''
13281469 return MapModule (mpstate )
0 commit comments