![]() |
VOOZH | about |
dotnet add package MapLargeInc.Cef.Avalonia --version 0.0.5-alpha
NuGet\Install-Package MapLargeInc.Cef.Avalonia -Version 0.0.5-alpha
<PackageReference Include="MapLargeInc.Cef.Avalonia" Version="0.0.5-alpha" />
<PackageVersion Include="MapLargeInc.Cef.Avalonia" Version="0.0.5-alpha" />Directory.Packages.props
<PackageReference Include="MapLargeInc.Cef.Avalonia" />Project file
paket add MapLargeInc.Cef.Avalonia --version 0.0.5-alpha
#r "nuget: MapLargeInc.Cef.Avalonia, 0.0.5-alpha"
#:package MapLargeInc.Cef.Avalonia@0.0.5-alpha
#addin nuget:?package=MapLargeInc.Cef.Avalonia&version=0.0.5-alpha&prereleaseInstall as a Cake Addin
#tool nuget:?package=MapLargeInc.Cef.Avalonia&version=0.0.5-alpha&prereleaseInstall as a Cake Tool
MapLarge.Cef.Avalonia is a library to integrate MapLarge in a browser and allow for communication from and to the .NET application itself. It is based on CEFGlue ("Chromium as a .NET UserControl") and AvaloniaUI and has a set of utilities to ease communication.
Integration with a browser is made possible through a technology called CEF, which stands for Chromium Embedded Framework. This allows two way communication with a Chromium browser inside a desktop application.
The MapLarge.CEF.Avalonia library provides an API to simplify the integration and communicates with a server-side ADK extension, called the 'CEF ADK extension'. ADK extensions are server side plugins and the CEF ADK extension is made to facilitate the communication specifically. So ensure this is installed in the same place as where your dashboard is deployed.
It's strongly recommended to have run through the MapLarge ADK Dashboard tutorials as this will get you familiar with MapLarge in general, provide understanding of the strengths of the query and layer options, and ADK development in general. However, the test application comes with an example dashboard to get you more familiar with the .NET side.
In your application, create a UserControl or Window and add a Decorator. This is the container for the browser user control:
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="AvaloniaExampleApp.YourUserControl">
<DockPanel>
...
<Decorator x:Name="browserWrapper" />
</DockPanel>
</UserControl>
Next up is connecting the decorator with the MapLarge specific Avalonia controller.
In this code below, the AvaloniaCefBrowser is the generic CEF component that is the basis for the communication, and the MapLargeBrowserController is the wrapper class that adds API to simplify the two-way communication.
public partial class YourUserControl : UserControl
{
private readonly MapLargeBrowserController _mapLargeController;
private readonly AvaloniaCefBrowser _browser;
public YourUserControl()
{
InitializeComponent();
_browser = new AvaloniaCefBrowser();
var url = "https://yoururl.com/dashboard";
_mapLargeController = new MapLargeBrowserController(_browser, url, null, true);
// Avalonia requires the browser window to be part of a decorator component.
var browserWrapperControl = this.FindControl<Decorator>("browserWrapper")!;
browserWrapperControl.Child = _browser;
}
}
From that point on, you can use methods as such:
private async void OnZoomToButtonClicked(object sender, EventArgs e) {
await _mapLargeController.MapZoom();
}
By default, MapLarge requires users to login before using a dashboard. This can be done manually, but the MapLargeBrowserController has an optional parameter to provide a login details provider as a Func, to automatically provide these details once MapLarge has been initialized. This is particularly helpful for desktop applications, where the credentials may be stored internally and you do not want to ask to log in each time the application is restarted.
Once the login completes, the users credentials are stored in the browser component and subsequent communication will continue passing the token on through the browser.
The requirement to be authenticated also means that certain functionality and data is only available after the login was successful, which is why there is an event on the MapLargeBrowserController called 'LoginCompleted'. This event will be invoked after login and serves as a way to complete the initialization of your application.
_mapLargeController = new MapLargeBrowserController(_browser, serverUrl, LoginDetailsProvider, devMode);
_mapLargeController.LoginCompleted += InitializeAfterLogin;
The MapLargeBrowserController has convenience methods for creating layers, etc, but since the Javascript is rich, dynamic and quickly evolving, the controller also provides a way to directly call Javascript functions. By returning the result of the function, the data can be directly be used. Note that, if working with async calls, you will need to use a callback method, which is explained below.
In the following example, we are retrieving a list of the available base layers ('satellite', 'dark', 'blank', etc):
// Get the available base layers by calling a specific MapLarge Javascript function directly
await _mapLargeController.RunScript("return ml.ui.map.BaseLayers.getCustomSupportedNames()", (string[] data) =>
{
foreach (var name in data)
{
// ..
}
});
Interactions in the browser window stay by default confined to the browser window, but there are situations where it is desired to have them trigger the .NET application. In the example below, we act on a mouse-over event in the browser to feed back the elevation underneath the mouse cursor.
First step is to start listening to the events:
_mapLargeController.MessageReceived += OnMessageReceived;
And the implementation:
private string OnMessageReceived(object sender, dynamic message)
{
var result = "";
// Note: Since Javascript is a dynamic language, we preserve this flexibility here.
// It is recommended to use this place to statically type the data
if (message is not Dictionary<string, object> messageInfo) return result;
var userMessageType = messageInfo["type"].ToString();
switch (userMessageType)
{
...
// Mouse hover callback
case "ElevationHover":
{
// Note the use of the dispatcher: The callback from the browser is pushed over an event on a separate thread,
// while the UI is rendered on the main thread
if (Double.TryParse(messageInfo["data"].ToString(), out var elevation))
{
Dispatcher.UIThread.Post(() => { HoveredElevation = elevation; });
}
break;
}
...
}
return result;
}
The typescript (dashboard) side of this looks similar to this:
private onElevationHovered(hoverInfo: any): boolean {
const elevation = Number.parseFloat(hoverInfo.data.DEFAULT);
cef.Admin.postBrowserMessage({
Type: 'ElevationHover',
Data: elevation
});
// Since we are redirecting the browser functionality, we'll return false to not show the hover popup in the browser
return false;
}
A few extra comments on this particular code:
MapLarge LayerJSON is a format in which MapLarge defines the layer. It roughly consists out of a query part and a style part. It is one of the most powerful aspects on MapLarge and allows for fast serverside database queries, combined with extensive ways of styling the layer.
Creating layers through MapLarge LayerJSON can be daunting, which is why some convenience functions are provided. Following is an example on how to create a simple serverside layer that visualizes the 'Hotels' table where the color of the location is determined by the star rating, which is a column in the table.
private async void CreateServerSideLayerButtonClicked(object sender, RoutedEventArgs e)
{
var layer = SimpleLayerBuilder.CreateBasicLayer("Hotels", LayerType.GeoDot);
layer.Query.Table.Name = "hms/hotels";
AddHotelStyling(layer);
await _mapLargeController.AddLayer(layer);
}
private void AddHotelStyling(Layer layer) {
var style1 = new Style {
FillColor = "255-0-170-0"
};
var style1Rule = new StyleRule(style1) {
Where = QueryWhere.Create("StarRating", TestSymbol.LessOR, 1)
};
layer.AddStyleRule(style1Rule);
...
// And a catch all
var catchAllStyle = new Style {
FillColor = "255-255-0-0"
};
var catchAllStyleRule = new StyleRule(catchAllStyle) {
Where = QueryWhere.Create("StarRating", TestSymbol.CatchAll, 0)
};
layer.AddStyleRule(catchAllStyleRule);
}
The more complex type of layer might be the clientside layer, which allows for data to be provided directly from the .NET side. This means that the data is not stored in the MapLarge Database, but directly rendered to the map. Note that this is not the most optimally performant option for MapLarge, but since it is the most complex to implement, this is chosen as example.
private async void onAddObjectsButtonClicked(object sender, EventArgs e)
{
var layer = SimpleLayerBuilder.CreateBasicLayer("YourLayerId", LayerType.GeoClientDot);
AddStylingToPointLayer(layer);
await _mapLargeController.AddClientSideLayer(layer, GetPointDataJson);
}
private void AddStylingToPointLayer(Layer layer) {
var blueStyle = new Style {
FillColor = "blue"
};
var redStyle = new Style {
FillColor = "red"
};
var styleRule = new StyleRule(blueStyle) {
Where = new QueryWhere(
new QueryWhereTest("id",
new QueryWhereTestTest(TestSymbol.Greater),
new QueryWhereValue(new QueryWhereLiteral(25_000))
)
)
};
// Or using the convenience method
var catchAllStyleRule = new StyleRule(redStyle) {
Where = QueryWhere.Create("id", TestSymbol.CatchAll, 0)
};
layer.AddStyleRule(styleRule);
layer.AddStyleRule(catchAllStyleRule);
}
In this example, the GetPointDataJson method is used as a callback method, which is automatically called the moment MapLarge
redraws the map. The result of this callback requires data similar to GeoJSON, but also in this case, MapLarge provides
convenience methods to construct this data.
private string GetPointDataJson(string layerId) {
var nObj = 50_000;
var objectsData = new ClientSideData();
// Define the column names
objectsData.Columns = new List<ClientSideDataColumn>() {
new ClientSideDataColumn("geom"),
new ClientSideDataColumn("id"),
new ClientSideDataColumn("name")
};
// Fill the data with the same ordered columns in the data for the amount of objects requested
for(var i = 0; i < nObj; i++) {
var id = i;
var name = "Object_" + i;
var lat = _randomizer.NextDouble() * (63 - 18) + 18;
var lon = (_randomizer.NextDouble() * (140 - 64) + 64) * -1;
// Note that the Point object has a custom JSON serializer
var geom = Point.FromLatLon(lat, lon);
var objectData = new List<object> {
geom,
id,
name
};
objectsData.Data.Add(objectData);
}
// Write the minimal amount of data, omiting default values
var options = new JsonSerializerOptions() {
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
var json = JsonSerializer.Serialize(objectsData, options);
return json;
}
There are convenience objects for creation of points, lines and polygons. However, if desired, this can be also done manually using arrays. For more information on ClientSide Layers, please refer to the ClientSide Layer document.
Just like Avalonia, MapLarge Dashboards use the MVVM paradigm to separate the data from the user interface. For that reason it also supports data binding, which subsequently can be used to communicate from the .NET side.
For example, there's an playbackPercentage member of the viewmodel of the dashboard, defined as such:
private _playbackPercentage: number = 0;
public get playbackPercentage(): number {
return this._playbackPercentage;
}
public set playbackPercentage(v: number) {
this._playbackPercentage = v;
}
Then in order to change this from the MapLarge controller side, a call to the SetProperty method on the MapLargeController
will do the update:
private void PlaybackSlider_OnValueChanged(object? sender, RangeBaseValueChangedEventArgs e)
{
...
_playbackIndex = newPlaybackIndex;
_mapLargeController.SetProperty("playbackPercentage", _playbackIndex);
}
Calling view model functions is similar to property calling, except it uses the ExecuteViewModelFunction, which allows for
parameterized function calling. In a previous example we showed how to communicate data back from the browser
to the .NET application and in this example, we show a practical example of this:
private async void AddWaypoint_OnClick(object? sender, RoutedEventArgs e)
{
await _mapLargeController.ExecuteViewModelFunction<Waypoint?>("startWaypointDrawing", SelectedAircraft.Name);
}
The respective function in the dashboard:
public startWaypointDrawing(aircraftName: string): void {
const aircraft = this._allAircraft.find(a => a.name === aircraftName);
if (!aircraft) throw new Error(`Aircraft with name ${aircraftName} not found`);
const rs = new ml.ui.map.RegionSelect({
map: this._map.map,
drawingType: "marker",
callback: (wkt, json, drawing) => {
const ll = ml.util.geo.wktPointToLatLng(wkt);
const lastWaypoint = aircraft.waypoints.slice(-1)[0];
const waypoint: IWaypoint = {
name: this.natoPhoneticAlphabet[aircraft.waypoints.length % this.natoPhoneticAlphabet.length],
latitude: +ll.lat,
longitude: +ll.lng,
altitude: lastWaypoint?.altitude ?? 100
};
cef.Admin.postBrowserMessage({
Type: "NewWaypoint",
Data: waypoint
});
}
});
rs.beginDrawing();
}
For completeness, we are showing the full implementation of the function here, including the postBrowserMessage call. This
call is needed because of the asynchronous nature of the callback.
Due to this postBrowserMessage call, the message handler on the .NET side will trigger (OnMessageReceived). The corresponding
code looks like this:
case "NewWaypoint":
{
var waypoint = JsonSerializer.Deserialize<Waypoint>(JsonSerializer.Serialize(messageInfo["data"]));
SelectedAircraft.Waypoints.Add(waypoint);
_mapLargeController.SetProperty("allAircraft", AllAircraft);
break;
}
Since we know the structure of a 'waypoint', we can use a JsonSerializer to deserialize this object into its static definition.
Included is a test application that shows the various concepts explained above and more, as:
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.0.5-alpha | 196 | 10/20/2025 |
| 0.0.4-alpha | 158 | 10/20/2025 |
| 0.0.3-alpha | 168 | 10/20/2025 |
| 0.0.2-alpha | 163 | 10/20/2025 |
| 0.0.1-alpha | 162 | 10/20/2025 |