X-Plane 11 Map API Sample

The sample below exercises the XPLM300 map layer API. Note that it depends on the image below (map-sample-image.png) being located in your X-Plane/Resources/plugins directory. If the image is missing, you will see a "missing resource" warning in your Log.txt file (and of course the icon drawing functionality of the map layer will instead draw nothing).

#include "XPLMMap.h"
#include "XPLMGraphics.h"
#include <string.h>
#include <stdio.h>
#if IBM
	#include <windows.h>
#endif
#if LIN
	#include <GL/gl.h>
#elif __GNUC__
	#include <OpenGL/gl.h>
#else
	#include <GL/gl.h>
#endif

XPLMMapLayerID		g_layer = NULL;

static int	coord_in_rect(float x, float y, const float bounds_ltrb[4])  {	return ((x >= bounds_ltrb[0]) && (x < bounds_ltrb[2]) && (y >= bounds_ltrb[3]) && (y < bounds_ltrb[1])); }

void createOurMapLayer(const char * mapIdentifier, void * refcon);

static void prep_cache(         XPLMMapLayerID layer, const float * inTotalMapBoundsLeftTopRightBottom, XPLMMapProjectionID projection, void * inRefcon);
static void draw_markings(      XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon);
static void draw_marking_icons( XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon);
static void draw_marking_labels(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon);
static void will_be_deleted(    XPLMMapLayerID layer, void * inRefcon);

PLUGIN_API int XPluginStart(
							char *		outName,
							char *		outSig,
							char *		outDesc)
{
	strcpy(outName, "MapPlugin");
	strcpy(outSig, "xpsdk.examples.mapplugin");
	strcpy(outDesc, "A test plug-in that demonstrates and exercises the X-Plane 11 map API.");
	return 1;
}

PLUGIN_API void	XPluginStop(void)
{
	// Clean up our map layer: if we created it, we should be good citizens and destroy it before the plugin is unloaded
	if(g_layer)
	{
		// Triggers the will-be-deleted callback of the layer, causing g_layer to get set back to NULL
		XPLMDestroyMapLayer(g_layer);
	}
}

PLUGIN_API int XPluginEnable(void)
{
	// We want to create our layer in the standard map used in the UI (not other maps like the IOS).
	// If the map already exists in X-Plane (i.e., if the user has opened it), we can create our layer immediately.
	// Otherwise, though, we need to wait for the map to be created, and only *then* can we create our layers.
	if(XPLMMapExists(XPLM_MAP_USER_INTERFACE))
	{
		createOurMapLayer(XPLM_MAP_USER_INTERFACE, NULL);
	}
	// Listen for any new map objects that get created
	XPLMRegisterMapCreationHook(&createOurMapLayer, NULL);
	return 1;
}
PLUGIN_API void XPluginDisable(void) { }
PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFrom, int inMsg, void * inParam) { }

void createOurMapLayer(const char * mapIdentifier, void * refcon)
{
	if(!g_layer && // Confirm we haven't created our markings layer yet (e.g., as a result of a previous callback), or if we did, it's been destroyed
			!strcmp(mapIdentifier, XPLM_MAP_USER_INTERFACE)) // we only want to create a layer in the normal user interface map (not the IOS)
	{
		XPLMCreateMapLayer_t params;
		params.structSize = sizeof(XPLMCreateMapLayer_t);
		params.mapToCreateLayerIn = XPLM_MAP_USER_INTERFACE;
		params.willBeDeletedCallback = &will_be_deleted;
		params.prepCacheCallback = &prep_cache;
		params.showUiToggle = 1;
		params.refcon = NULL;
		params.layerType = xplm_MapLayer_Markings;
		params.drawCallback = &draw_markings;
		params.iconCallback = &draw_marking_icons;
		params.labelCallback = &draw_marking_labels;
		params.layerName = "Markings";
		// Note: this could fail (return NULL) if we hadn't already confirmed that params.mapToCreateLayerIn exists in X-Plane already
		g_layer = XPLMCreateMapLayer(&params);
	}
}

int s_num_cached_coords = 0;
#define MAX_COORDS (360 * 180)
float s_cached_x_coords[MAX_COORDS]; // The map x coordinates at which we will draw our icons; only the range [0, s_num_cached_coords) are valid
float s_cached_y_coords[MAX_COORDS]; // The map y coordinates at which we will draw our icons; only the range [0, s_num_cached_coords) are valid
float s_cached_lon_coords[MAX_COORDS]; // The real latitudes that correspond to our cached map (x, y) coordinates; only the range [0, s_num_cached_coords) are valid
float s_cached_lat_coords[MAX_COORDS]; // The real latitudes that correspond to our cached map (x, y) coordinates; only the range [0, s_num_cached_coords) are valid
float s_icon_width = 0; // The width, in map units, that we should draw our icons.

void prep_cache(XPLMMapLayerID layer, const float * inTotalMapBoundsLeftTopRightBottom, XPLMMapProjectionID projection, void * inRefcon)
{
	// We're simply going to cache the locations, in *map* coordinates, of all the places we want to draw.
	s_num_cached_coords = 0;
	for(int lon = -180; lon < 180; ++lon)
	{
		for(int lat =  -90; lat <  90; ++lat)
		{
			float x, y;
			const float offset = 0.25; // to avoid drawing on grid lines
			XPLMMapProject(projection, lat + offset, lon + offset, &x, &y);
			if(coord_in_rect(x, y, inTotalMapBoundsLeftTopRightBottom))
			{
				s_cached_x_coords[s_num_cached_coords] = x;
				s_cached_y_coords[s_num_cached_coords] = y;
				s_cached_lon_coords[s_num_cached_coords] = lon + offset;
				s_cached_lat_coords[s_num_cached_coords] = lat + offset;
				++s_num_cached_coords;
			}
		}
	}

	// Because the map uses true cartographical projections, the size of 1 meter in map units can change
	// depending on where you are asking about. We'll ask about the midpoint of the available bounds
	// and assume the answer won't change too terribly much over the size of the maps shown in the UI.
	const float midpoint_x = (inTotalMapBoundsLeftTopRightBottom[0] + inTotalMapBoundsLeftTopRightBottom[2]) / 2;
	const float midpoint_y = (inTotalMapBoundsLeftTopRightBottom[1] + inTotalMapBoundsLeftTopRightBottom[3]) / 2;
	// We'll draw our icons to be 5000 meters wide in the map
	s_icon_width = XPLMMapScaleMeter(projection, midpoint_x, midpoint_y) * 5000;
}

void draw_markings(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon)
{
	// The arbitrary OpenGL drawing done for our markings layer.
	// We will simply draw a green box around the icon; the icon itself will be enqueued when we get a callback to draw_marking_icons().

	XPLMSetGraphicsState(
			0 /* no fog */,
			0 /* 0 texture units */,
			0 /* no lighting */,
			0 /* no alpha testing */,
			1 /* do alpha blend */,
			1 /* do depth testing */,
			0 /* no depth writing */
	);

	glColor3f(0, 1, 0); // green

	const float half_width = s_icon_width / 2;
	const float half_height = half_width * 0.6667; // our images are in a 3:2 aspect ratio, so the height is 2/3 the width
	for(int coord = 0; coord < s_num_cached_coords; ++coord)
	{
		const float x = s_cached_x_coords[coord];
		const float y = s_cached_y_coords[coord];
		if(coord_in_rect(x, y, inMapBoundsLeftTopRightBottom))
		{
			// Draw the box around the icon (we use half the width and height, since the icons will be *centered* at this (x, y)
			glBegin(GL_LINE_LOOP);
			{
				glVertex2f(x - half_width, y + half_height);
				glVertex2f(x + half_width, y + half_height);
				glVertex2f(x + half_width, y - half_height);
				glVertex2f(x - half_width, y - half_height);
			}
			glEnd();
		}
	}
}

void draw_marking_icons(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon)
{
	for(int coord = 0; coord < s_num_cached_coords; ++coord)
	{
		const float x = s_cached_x_coords[coord];
		const float y = s_cached_y_coords[coord];
		if(coord_in_rect(x, y, inMapBoundsLeftTopRightBottom))
		{
			#define SAMPLE_IMG "Resources/plugins/map-sample-image.png"
			if(coord % 2)
			{
				XPLMDrawMapIconFromSheet(
						layer, SAMPLE_IMG,
						0, 0, // draw the image cell at (s, t) == (0, 0) (i.e., the bottom left cell in the sample image)
						2, 2, // our sample image is two image cell wide, and two image cells tall
						x, y,
						xplm_MapOrientation_Map, // Orient the icon relative to the map itself, rather than relative to the UI
						0, // Zero degrees rotation
						s_icon_width);
			}
			else
			{
				// Draw the image at cell (s, t) == (1, 1) (i.e., the top right cell in the sample image)
				XPLMDrawMapIconFromSheet(layer, SAMPLE_IMG, 1, 1, 2, 2, x, y, xplm_MapOrientation_Map, 0, s_icon_width);
			}
		}
	}
}

void draw_marking_labels(XPLMMapLayerID layer, const float * inMapBoundsLeftTopRightBottom, float zoomRatio, float mapUnitsPerUserInterfaceUnit, XPLMMapStyle mapStyle, XPLMMapProjectionID projection, void * inRefcon)
{
	if(zoomRatio >= 18) // don't label when zoomed too far out... everything will run together in a big, illegible mess
	{
		for(int coord = 0; coord < s_num_cached_coords; ++coord)
		{
			const float x = s_cached_x_coords[coord];
			const float y = s_cached_y_coords[coord];
			if(coord_in_rect(x, y, inMapBoundsLeftTopRightBottom))
			{
				char scratch_buffer[150];
				sprintf(scratch_buffer, "%0.2f / %0.2f Lat/Lon", s_cached_lat_coords[coord], s_cached_lon_coords[coord]);
				
				// The text will be centered at the (x, y) we pass in. But, instead of drawing the label in the center
				// of the icon, we'd really like the text to be shifted down *beneath* the icon we drew,
				// so we'll subtract some amount from the y coordinate
				const float icon_bottom = y - s_icon_width / 2;
				const float text_center_y = icon_bottom - (mapUnitsPerUserInterfaceUnit * icon_bottom / 2); // top of the text will touch the bottom of the icon
				XPLMDrawMapLabel(layer, scratch_buffer, x, text_center_y, xplm_MapOrientation_Map, 0);
			}
		}
	}
}

void will_be_deleted(XPLMMapLayerID layer, void * inRefcon)
{
	if(layer == g_layer)
		g_layer = NULL;
}



  • Facebook
  • Reddit
  • Twitter
  • LinkedIn

About Tyler Young

Tyler is a software developer for X-Plane. Among other projects, he was in charge of the new X-Plane 11 user interface.
Bookmark the permalink.

2 Responses to X-Plane 11 Map API Sample

  1. saar says:

    Thanks for the example.
    One short question though, Will the GL code be replaced once you will move to "full" Vulkan/Metal drawing framework, or can they co-exists, the new one only for XPlane 3D space and the map can work with "legacy" code even in the future.

    Thanks
    Saar

    • Ben Supnik says:

      Our _design goal_* is for the map, panel, and 2-d UI to keep working with plugin-based OpenGL code.

      I say "design goal" because we do not have this already coded, or even a working prototype, so if we find out that this is impossible due to the GL and Vulkan APIs, it won't happen. But it is what we want to have happen and it will be our first attempt to build something.

Leave a Reply

Your email address will not be published. Required fields are marked *