@@ -27,6 +27,7 @@ def __init__(self, mpstate):
27
27
self .wp_change_time = 0
28
28
self .fence_change_time = 0
29
29
self .rally_change_time = 0
30
+ self .terrain_contour_ids = []
30
31
self .have_simstate = False
31
32
self .have_vehicle = {}
32
33
self .move_wp = - 1
@@ -65,8 +66,12 @@ def __init__(self, mpstate):
65
66
('showdirection' , bool , False ),
66
67
('setpos_accuracy' , float , 50 ),
67
68
('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
+
70
75
service = 'MicrosoftHyb'
71
76
if 'MAP_SERVICE' in os .environ :
72
77
service = os .environ ['MAP_SERVICE' ]
@@ -141,6 +146,12 @@ def __init__(self, mpstate):
141
146
mavutil .mavlink .MAV_CMD_NAV_VTOL_LAND : "VL" ,
142
147
}
143
148
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
+
144
155
def add_menu (self , menu ):
145
156
'''add to the default popup menu'''
146
157
from MAVProxy .modules .mavproxy_map import mp_slipmap
@@ -192,7 +203,6 @@ def print_google_maps_link(self):
192
203
pos = self .mpstate .click_location
193
204
print ("https://www.google.com/maps/search/?api=1&query=%f,%f" % (pos [0 ], pos [1 ]))
194
205
195
-
196
206
def write_JSON (self , fname , template , vardict ):
197
207
'''write a JSON file in log directory'''
198
208
fname = os .path .join (self .logdir , fname )
@@ -268,8 +278,6 @@ def cmd_map_marker(self, args, latlon=None):
268
278
269
279
print ("Wrote marker %s" % fname )
270
280
271
-
272
-
273
281
def cmd_map (self , args ):
274
282
'''map commands'''
275
283
from MAVProxy .modules .mavproxy_map import mp_slipmap
@@ -650,7 +658,6 @@ def display_fence(self):
650
658
else :
651
659
self .map .remove_object ('Fence' )
652
660
653
-
654
661
def closest_waypoint (self , latlon ):
655
662
'''find closest waypoint to a position'''
656
663
(lat , lon ) = latlon
@@ -778,6 +785,12 @@ def handle_menu_event(self, obj):
778
785
self .print_google_maps_link ()
779
786
elif menuitem .returnkey == 'setServiceTerrain' :
780
787
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 ()
781
794
782
795
def map_callback (self , obj ):
783
796
'''called when an event happens on the slipmap'''
@@ -1323,6 +1336,134 @@ def check_redisplay_rallypoints(self):
1323
1336
points .append ((nearest_land_wp .x , nearest_land_wp .y ))
1324
1337
self .map .add_object (mp_slipmap .SlipPolygon ('Rally Land %u' % (i + 1 ), points , 'RallyPoints' , (255 ,255 ,0 ), 2 ))
1325
1338
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
+
1326
1467
def init (mpstate ):
1327
1468
'''initialise module'''
1328
1469
return MapModule (mpstate )
0 commit comments