Skip to main content

Creating a Custom Chart Provider

Introduction

The Charts API supports chart providers beyond the two built-in providers that the sim offers. Providers other than the built-in providers are called custom chart providers. Custom chart providers can be used to serve third-party chart data to consumers that use the Charts API.

There are three general steps when it comes to creating your own custom chart provider. First, you must organize your chart data into data structures that conform to the Charts API specifications. Second, you must create a class that allows consumers to retrieve your chart data. And third, you must create a class that serves chart images via image URLs that can be used to load and display your charts.

The following sections will walk you through each of these steps in detail.

Choosing a Provider Name

Before you do anything else, you must choose a name for your custom chart provider. When choosing a name, keep in mind that it must be unique among all chart providers. Therefore, it cannot be the same as those of the built-in providers, which are defined in the BuiltInChartProvider enum. Also, the name should be sufficiently descriptive to not be likely to conflict with other potential custom chart providers. Finally, the name cannot be the empty string.

Organizing Your Chart Data

When providing your chart data to consumers, it must be organized into structures that conform to the specifications defined by the Charts API. Please refer to this page to familiarize yourself with the general concepts and structure of chart data in the Charts API.

Chart Index

Your custom chart provider must be able to provide one chart index (ChartIndex) for any given airport. The chart index can be empty for airports that have no available charts. Otherwise, the index should organize the charts available for its airport into one or more categories (ChartIndexCategory).

Each chart category should have a unique name. You can define as few or as many categories as you wish, and you can organize your charts into the categories using any arbitrary logic. However, categories and their semantics should remain consistent across all chart indexes.

Chart Metadata

Each chart available for an airport must be associated with a metadata object (ChartMetadata). These metadata objects should be stored in arrays found in their parent chart category objects.

The metadata object must contain the following properties:

PropertyDescriptionRemarks
providerThe name of the provider from which the chart data was obtained.This should be the name of your custom chart provider.
guidThe GUID assigned to the chart.The GUID will be used to uniquely identify and reference the chart. It is recommended to use specification-compliant 128-bit GUIDs. GUIDs must be stable - once a chart has been assigned a GUID, the assignment cannot change during the lifetime of the process that is accessing the chart data.
typeThe category to which the chart belongs.Must be the same as the name of the chart category that contains the metadata object.
airportIcaoThe ICAO of the airport associated with the chart.
nameThe name of the chart.Does not have to follow any particular standard naming scheme. However, it is recommended that the naming be kept consistent at least within your custom chart provider.
geoReferencedWhether any of the chart's pages are geoferenced.See this section.
proceduresAll procedures associated with the chart.See this section.
runwaysAll runways associated with the chart.
aircraftTypesAll aircraft types to which the chart applies.If the chart has no aircraft type restrictions, then the array should remain empty. There is no required naming scheme for aircraft types. However, it is recommended that the naming be kept consistent for all charts provided by your custom chart provider.
relationshipsA list of relationships from this chart to other charts.See this section. The ability to define relationships is provided for convenience. You may choose which relationships to define for your charts, if any, and what those relationships represent.
validFromThe date from which the chart is valid, inclusive, or null if the date is not available.Expressed as a Javascript timestamp (milliseconds since the UNIX epoch).
validUntilThe date to which the chart is valid, inclusive, or null if the date is not available.Expressed as a Javascript timestamp (milliseconds since the UNIX epoch).

Chart Procedure Identifiers

Each chart procedure identifier references a particular published instrument procedure at an airport. Procedure identifiers can either reference a departure/arrival procedure (ChartSidStarProcedureIdentifier) or an approach procedure (ChartApproachProcedureIdentifier).

A procedure identifier may reference a procedure as a whole or a subset of a procedure. For example, if a chart depicts a departure procedure but only for certain runway transitions, the procedure identifier used to reference the departure procedure for the chart should include only those runways that the chart depicts rather than every runway available for the departure.

ChartSidStarProcedureIdentifier must contain the following properties:

PropertyDescriptionRemarks
typeThe type of the referenced procedure.Either ChartProcedureType.Sid or ChartProcedureType.Star.
identThe referenced procedure's string identifier.This should, if possible, match the value of the name property of the corresponding Procedure object that can be accessed from the AirportFacility object provided by the sim's nav data.
enrouteTransitionThe string identifier for the (enroute) transition of the referenced procedure.This should, if possible, match the value of the name property of the corresponding EnrouteTransition object that can be accessed from the AirportFacility object provided by the sim's nav data. If you wish to reference a procedure that includes all of its possible enroute transitions, it is acceptable to include one instance of a procedure identifier with this property set to null.
runwaysA list of runways associated with the referenced procedure.Each runway is referenced using a RunwayIdentifier object.
approachIdentifierShould always be null.
runwayTransitionDeprecated. Should always be null.

ChartApproachProcedureIdentifier must contain the following properties:

PropertyDescriptionRemarks
typeThe type of the referenced approach.Must be ChartProcedureType.Approach.
approachIdentifierThe referenced approach's approach identifier.An ApproachIdentifier object.
enrouteTransitionThe string identifier for the transition of the referenced approach.This should, if possible, match the value of the name property of the corresponding ApproachTransition object that can be accessed from the AirportFacility object provided by the sim's nav data. If you wish to reference an approach that includes all of its possible transitions, it is acceptable to include one instance of a procedure identifier with this property set to null.
runwaysA list of runways associated with the referenced approach.Each runway is referenced using a RunwayIdentifier object.
identShould always be the empty string.
runwayTransitionDeprecated. Should always be null.

Chart Relationships

Relationships between two charts can be represented using ChartRelationship objects. Each ChartRelationship represents a one-way relationship from one chart to another chart. To represent a bidirectional relationship, use two ChartRelationship objects.

ChartRelationship must contain the following properties:

PropertyDescriptionRemarks
typeThe type of the relationship.This is a numeric value that extends the ChartRelationshipType enum. You are free to define your own relationship types that are not members of ChartRelationshipType. However, if you do use members of ChartRelationshipType, their use must be consistent with the established semantics of those members.
fromChartGuidThe GUID of the from chart in the relationship.
toChartGuidThe GUID of the to chart in the relationship.
toChartPageThe (zero-based) index of the page of the to chart to which the relationship applies.If the relationship does not apply to a specific page (or the to chart only has a single page), then this property should be null.
procedureThe procedure associated with the relationship.See this section. If the relationship is not associated with any procedure, then this property should be null.

Chart Page

Each chart from your provider may include one or more pages. It is up to you how to organize your chart pages; there is no requirement for which pages should be bundled together as a single chart and which pages should be included as standalone charts.

Pages for a single chart are organized into a ChartPages object. Individual chart pages are represented by ChartPage objects, which are contained in an array in their parent ChartPages object.

ChartPage must contain the following properties:

PropertyDescriptionRemarks
widthThe width of the page.Measured in arbitrary units (does not have to be pixels). The chosen units must be the same as those used for height.
heightThe height of the page.Measured in arbitrary units (does not have to be pixels). The chosen units must be the same as those used for width.
geoReferencedWhether the page is georeferenced.Should be true if and only if the page has at least one chart area that is georeferenced.
areasA list of areas defined for the page.You can choose to define as many or as few areas as you wish for any given chart page. Areas are allowed to overlap.
urlsA list of chart URLs that can be used to retrieve the page as an image or document.Include one URL for every unique image or document asset available for the chart. For example, you can include a URL for a light color mode version of the chart and another for a dark color mode version.

Chart URLs

Chart URLs are used to reference image or document assets for chart pages. They are represented by ChartUrl objects.

Each chart URL is assigned a name and a URL string. The name is for description and disambiguation only. You may use any naming scheme you wish. The URL string uniquely identifies the URL's associated asset. Even though it is termed a "URL", the URL string does not necessarily have to define an addressable location. The only requirements for the string are that it cannot be the empty string, it must uniquely identify and be able to be used to reference its associated asset, and it must be stable (once an asset has been assigned a URL string, the assignment cannot change during the lifetime of the process that is accessing the chart data).

Chart Areas

A chart area is a rectangular region within a chart page. It is up to you to define what constitutes an "area" in one of your chart pages. Each area can encompass any region within the page (up to an including the entire page).

Each chart area is represented by a ChartArea object. Chart areas can be either non-georeferenced or georeferenced. Non-georeferenced areas are represented by NonGeoReferencedChartArea(/api/@microsoft/msfs-sdk/interfaces/NonGeoReferencedChartArea.md), and georeferenced areas are represented by GeoReferencedChartArea(/api/@microsoft/msfs-sdk/interfaces/GeoReferencedChartArea.md).

NonGeoReferencedChartArea must contain the following properties:

PropertyDescriptionRemarks
layerThe layer name assigned to the area.The name should be descriptive, but there is no required naming scheme.
chartRectangleThe geometry of the area.A ChartRectangle that defines the coordinates of the upper-left and lower-right corners of the area. The coordinates should used the same units used to define the parent page's width and height. The origin is located at the upper-left corner of the parent page, with the positive x axis pointing right and the positive y axis pointing down. The orientation of the rectangle should be set to zero.
geoReferencedWhether the area is georeferenced.Must be false.

GeoReferencedChartArea must contain the following properties:

PropertyDescriptionRemarks
layerThe layer name assigned to the area.The name should be descriptive, but there is no required naming scheme.
chartRectangleThe geometry of the area within its parent page.A ChartRectangle that defines the coordinates of the upper-left and lower-right corners of the area. The coordinates should used the same units used to define the parent page's width and height. The origin is located at the upper-left corner of the parent page, with the positive x axis pointing right and the positive y axis pointing down. The orientation of the rectangle should be set to zero.
worldRectangleThe geometry of the geographical area to which the chart area is referenced.A ChartRectangle that defines the latitude/longitude coordinates that are mapped to the top-left and lower-right corners of the chart area. The coordinates should be expressed in degrees. See this section for how to define the rectangle's orientation.
projectionThe parameters that define the projection used to map geographical coordinates to chart page coordinates within the area.See this section for how to define these parameters.
geoReferencedWhether the area is georeferenced.Must be true.

Georeferenced Chart Area Parameters

When defining a georeferenced chart area, you must provide parameters that define the Lambert conformal conic (LCC) projection that is used to map geographical coordinates within the area's world rectangle to chart page coordinates within the area's chart rectangle. There are four projection parameters to define. Three of them are defined using the ChartLambertConformalConicProjection object found in the chart area's projection property. The last is defined using the orientation property of the chart area's world rectangle.

ChartLambertConformalConicProjection defines the projection's two standard parallels and central meridian. If the projection uses the one standard parallel form, then specify the same standard parallel in both standard parallel properties. The parallels should be expressed in standard latitude coordinates using degrees, and the meridian should be expressed in standard longitude coordinates using degrees.

The orientation property of the area's world rectangle defines the rotation of the projected coordinates. In other words, it defines the amount by which the "true north" direction, as measured along the projected central meridian, is rotated away from the "up" direction on the chart. The orientation should be expressed in degrees, with positive values indicating a counterclockwise rotation (i.e. true north is rotated counterclockwise from the up direction).

tip

If your georeferenced charts use a Mercator projection instead of an LCC projection, you can still use LCC projection parameters to define the projection. This is because the Mercator projection can be represented as a special case of the LCC projection.

To define a Mercator projection using the LCC parameters, set both standard parallels to zero degrees (the equator). It is also recommended to set the central meridian to the meridian that passes through the center of the georeferenced area (or something close to it). Setting the central meridian in this way will prevent issues related to anti-meridian wrapping. The orientation parameter can be defined in the same way as for (non-Mercator) LCC projections.

Creating a Chart Service

Once you've figured out how to organize your chart data, the next step is to create a class that allows consumers to access it. This class must implement the ChartService interface.

There are two methods the class must have: getIndexForAirport() retrieves a chart index given a provider and airport ICAO value, and getChartPages() retrieves chart page data given a chart GUID. How these methods retrieve their data is up to you to decide and implement. If either method encounters an error that prevents it from retrieving the requested data, they should reject the returned Promise with one of the error codes defined in the ChartServiceErrorCode enum.

info

When a chart index is requested for an airport without any available charts, getIndexForAirport() should return an empty chart index instead of rejecting the request with a NotFound error.

The following code shows an example of how one might implement a ChartService. Note that the code to fetch the chart data has been intentionally left out since those details will be specific to each custom chart provider.

import { ChartPages, ChartService, ChartServiceErrorCode, IcaoValue } from '@microsoft/msfs-sdk';

class MyChartService implements ChartService {
public getIndexForAirport(provider: string, airportIcao: IcaoValue): Promise<ChartIndex<string>> {
try {
return await this.fetchChartIndex(provider, airportIcao);
} catch (e) {
const errorCode: ChartServiceErrorCode = ... // choose an appropriate error code
throw errorCode;
}
}

private async fetchChartIndex(provider: string, airportIcao: IcaoValue): Promise<ChartIndex<string>> {
// fetch chart index...
}

public getChartPages(chartGuid: string): Promise<ChartPages> {
try {
return await this.fetchChartPages(provider, airportIcao);
} catch (e) {
const errorCode: ChartServiceErrorCode = ... // choose an appropriate error code
throw errorCode;
}
}

private async fetchChartPages(chartGuid: string): Promise<ChartPages> {
// fetch chart index...
}
}

Creating a Chart Image Supplier

The final step is to create a class that allows consumers to retrieve images for your charts. This class must implement the ChartImageSupplier interface.

When the showChartImage() method on your ChartImageSupplier class is called, it should begin the process of preparing the image asset for the requested chart page URL. Image preparation can be an asynchronous operation; it does not need to finish before showChartImage() returns. Once image preparation is finished, the asset should be able to be loaded from a URL accessible by CoherentGT views from within the sim. At this point, the supplier's image subscribable should be updated to provide a ChartImage object that contains the URL from which the chart image can be accessed (the image URL). If the supplier fails to retrieve an image URL for the requested chart page URL, then image should be updated with a ChartImage object that has an image URL equal to the empty string and that contains one of the error codes defined in the ChartImageErrorCode enum.

If showChartImage() is called with the empty string as its argument, then this should be interpreted as a request to clear the supplied image. The previous supplied image (if one exists) can be cleaned up if necessary and the image subscribable should be updated with an image URL equal to the empty string.

note

While image preparation for a new chart page URL is ongoing, the previous supplied image (if one exists) should be kept available and the supplier's image subscribable should continue to provide data pertaining to the previous supplied image. Only when the new image is ready should the image subscribable be updated with data for the new image.

The destroy() method should contain any cleanup code that is necessary to release resources used by the supplier when it is no longer needed. You should assume that when destroy() is called it is also safe to clean up any image assets that were previously prepared by the supplier.

The following code shows an example of how one might implement a ChartImageSupplier. Note that the code to prepare the image has been intentionally left out since those details will be specific to each custom chart provider.

import { ChartImage, ChartImageErrorCode, ChartImageSupplier, ChartUrl, Subject } from '@microsoft/msfs-sdk';

class MyChartImageSupplier implements ChartImageSupplier {
private readonly _image = Subject.create<ChartImage>({
imageUrl: '',
chartUrl: '',
errorCode: ChartImageErrorCode.None,
});
public readonly image = this._image as Subscribable<ChartImage>;

public showChartImage(chartUrl: string): void {
this.prepareChartImage(chartUrl);
}

private async prepareChartImage(chartUrl: string): Promise<void> {
try {
// do image preparation...
// ...
// ...

const imageUrl = ... // get the image URL
this._image.set({
imageUrl,
chartUrl,
errorCode: ChartImageErrorCode.None,
});
} catch (e) {
const errorCode: ChartImageErrorCode = ... // choose an appropriate error code
this._image.set({
imageUrl: '',
chartUrl,
errorCode,
});
}
}

public destroy(): void {
// cleanup code here.
}
}