1

I have created a .NET MAUI desktop application that reads "hitrorical data" form an SD card that is in a dummy LoRa network scanner. As displayed in the image below, the map shows the different positions of the scanner relative to the "dummy" LoRa transmitter during scanning and in different scans. Instead, I would like to create a heatmap layer based on the signal strength (RSSI) value.

enter image description here

I have been looking online for a way to do it, but to no vail. The closest thing I stumbled upon is the following link: Analyze hotspots. However, I am having a hard time to tweak it to fit my needs, to a point that I am doubting it as the correct solution.

Would anybody please give me a tip to how to approach this issue? Please note that I am quite new to C# and .NET MAUI.

I am looking to generate a map that looks like:

Image source: https://learn.microsoft.com/en-us/bingmaps/v8-web-control/map-control-concepts/heat-map-module-examples/basic-heat-map-example

Update!!

So I have read the article recommended by Nixta, and made the following code based on the article. However, when I click on the "UpdateRendererButton_Clicked" Button I don't get any haet map generated, instead the original map points disappear.

HeatMapRenderer.cs:

using Newtonsoft.Json;
namespace HeatMapRendererJson

public partial class HeatMapRenderer
{
    public string ToJson()
    {
        return JsonConvert.SerializeObject(this);
    }

    public void AddColorStop(double ratio, Color color)
    {
        if (ratio > 1.0 || ratio < 0.0) { throw new Exception("Argument 'ratio' must be a value between 0 and 1."); };

        ColorStop stop = new ColorStop(ratio, color);
        ColorStops.Add(stop);
    }
  
    public void ClearColorStops()
    {
        ColorStops.Clear();
    }

    [JsonProperty("type")]
    public string Type { get; set; } = "heatmap";


    [JsonProperty("blurRadius")]
    public long BlurRadius { get; set; }

    [JsonProperty("colorStops")]
    public List<ColorStop> ColorStops { get; set; } = new List<ColorStop>();

    [JsonProperty("field")]
    public string Field { get; set; }

    [JsonProperty("maxPixelIntensity")]
    public double MaxPixelIntensity { get; set; }

    [JsonProperty("minPixelIntensity")]
    public double MinPixelIntensity { get; set; }
}

public partial class ColorStop
{

    [JsonProperty("ratio")]
    public double Ratio { get; set; }

    [JsonProperty("color")]
    public int[] Color { get; set; }

    public ColorStop(double ratio, Color color)
    {
        Ratio = ratio;
        Color = new int[] { (int)color.Red, (int)color.Green, (int)color.Blue, (int)color.Alpha };
    }
}
}

MainWindow.xaml.cs:

public partial class MainWindow : ContentPage
{
// URL to a sample layer with earthquake points.
private string _earthquakesUrl ="https://sampleserver6.arcgisonline.com/arcgis/rest/services/Earthquakes_Since1970/FeatureServer/0";

// Store a reference to the quakes layer and its default renderer.
private FeatureLayer _quakesLayer;
private Renderer _defaultRenderer;

private List<Field> numericFields = new List<Field>();

public MainWindow()
{
    InitializeComponent();
    Init();
    Esri.ArcGISRuntime.ArcGISRuntimeEnvironment.ApiKey = "myKey";
}
private async void Init()
{
    // Create the OpenStreetMap basemap.
    Basemap osmBasemap = new Basemap(BasemapStyle.OSMStandard);

    // Create the map with the OpenStreetMap basemap.
    Map map = new Map(osmBasemap);

    // Create the earthquake layer, load it, and get its default renderer.
    _quakesLayer = new FeatureLayer(new Uri(_earthquakesUrl));
    await _quakesLayer.LoadAsync();
    _defaultRenderer = _quakesLayer.Renderer;

    // Add the quakes layer to the map, add the map to the map view.
    map.OperationalLayers.Add(_quakesLayer);
    MyMapView.Map = map;
   
    int x =1;
    foreach (var fld in _quakesLayer.FeatureTable.Fields)
    {
        if (fld.FieldType.HasFlag(FieldType.Float32 | FieldType.Float64 | FieldType.Int16 | FieldType.Int32))
        {
            numericFields.Add(fld);
        }
        Debug.WriteLine(x + "-fields: " + fld);
        x++;
    }
    Debug.WriteLine("numericFields: " + numericFields[11]);

    private void UpdateRendererButton_Clicked(object sender, EventArgs e)
    {
    // Parse some of the user inputs.
    double.TryParse(MinIntensityTextBox.Text, out double minIntensity);
    double.TryParse(MaxIntensityTextBox.Text, out double maxIntensity);
    //int.TryParse(BlurRadiusComboBox.SelectedItem.ToString(), out int blurRadius);

    // Create a new HeatMapRenderer with info provided by the user.
    HeatMapRenderer heatMapRendererInfo = new HeatMapRenderer
    {
        BlurRadius = 6,
        MaxPixelIntensity = 1000.0,
        MinPixelIntensity = 0.0

    };

     heatMapRendererInfo.Field = numericFields[11].Name;
     heatMapRendererInfo.AddColorStop(0.0, Colors.Transparent);
    heatMapRendererInfo.AddColorStop(0.10, new Color(1, 0, 0, (float)0.6));    // Red color at position 0
    //heatMapRendererInfo.AddColorStop(0.5, new Color(1, 1, 0, (float)0.6));  // Yellow color at position 0.5
    heatMapRendererInfo.AddColorStop(1.0, new Color(0, 1, 0, (float)0.6));    // Green color at position 1
         // Get the JSON representation of the renderer class.
    string heatMapJson = heatMapRendererInfo.ToJson();

    // Use the static Renderer.FromJson method to create a new renderer from the JSON string.
    var heatMapRenderer = Renderer.FromJson(heatMapJson);

    // Apply the renderer to a point layer in the map.
    _quakesLayer.Renderer = heatMapRenderer;
}

private void ResetRenderer_Clicked(object sender, EventArgs e)
{
    // Reapply the default renderer for the layer.
    _quakesLayer.Renderer = _defaultRenderer;
}
}

and finally the MainWindow.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Scanner_MAUI.Pages.MainWindow"
             Title="MainWindow"
             xmlns:esri="http://schemas.esri.com/arcgis/runtime/2013">
    <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="300"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid Grid.Column="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition Height="35"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
 <Button Grid.Row="7" Grid.ColumnSpan="2"
                    Text="Update renderer"
                    Width="120" Height="25"
                    Clicked="UpdateRendererButton_Clicked"/>
        <Button Grid.Row="8" Grid.ColumnSpan="2"
                    Text="Reset"
                    Width="120" Height="25"
                    Clicked="ResetRenderer_Clicked"/>
    </Grid>
    <esri:MapView x:Name="MyMapView"
                      Grid.Column="1"/>
    </Grid>
</ContentPage>
BaselE
  • 43
  • 5

1 Answers1

0

We don’t have a full API around the heat map renderer, as you noticed, but you can construct appropriate JSON (as the Map Viewer does) and construct it from that. Take a look at this article which walks through it.

Nixta
  • 464
  • 2
  • 10
  • Thanks for the answer. I have read the artice and updated my question. I still don't get the heatmap. Would you please take a look if possible? – BaselE Jul 27 '23 at 17:30
  • Hard to say, but are you using the right field? Did you get the sample app from that article to work? – Nixta Jul 27 '23 at 18:06
  • The code I added to the question is the sample app. I chaneg it a bit to fit the dotnet MAUI. So I only have now the buttons, and I hardcoded the values in the "heatMapRenderer" object. But still, it doesn't work :( With the code above I think it is possible to re-create the issue. And yeah I am using the "magnitude" field specified in the article. It should be correct I think. – BaselE Jul 27 '23 at 18:14
  • So if you open and run the sample app unmodified it doesn’t work for you? – Nixta Jul 27 '23 at 18:28
  • At least not in my project. Well I only copied the files to my project and they were full of errors so I had to modify them. I think the article utilizes .NET WPF – BaselE Jul 27 '23 at 18:33
  • 1
    If the sample works but your project doesn’t, you’ll need to figure out the difference. I’m not a .NET developer, so I can’t run either set of code, but you could usefully ask the question in the actual SDK forums: https://community.esri.com/t5/net-maps-sdk-questions/bd-p/arcgis-runtime-sdk-dotnet-questions – Nixta Jul 27 '23 at 18:39
  • Thanks for the tips. I'll seek help there. – BaselE Jul 27 '23 at 18:41