.NET MAUI

AI-Driven Smart Location Search in .NET MAUI Maps

TL;DR: Let’s see how to integrate OpenAI with Syncfusion .NET MAUI Maps for AI-powered smart location search. We’ll use Azure OpenAI for generating map markers, set up .NET MAUI Autocomplete for user input, and design custom markers with tooltips. This integration enhances map-based searches with AI-generated geographic details.

Unlock the power of AI to effortlessly locate specific places or landmarks by integrating OpenAI’s advanced services into Syncfusion .NET MAUI Maps.

In this blog, we will explore how to achieve this integration, making it easier for users to find exact locations on the map.

Let’s get started!

Integrating Azure OpenAI with the .NET MAUI app

First, open Visual Studio and create a new .NET MAUI app.

Before unlocking the power of AI to locate specific places effortlessly, ensure that you have access to Azure OpenAI and have set up a deployment in the Azure portal. You can find the Azure.AI.OpenAI NuGet package in the NuGet Gallery and install it in the project.

Once you get your key and endpoint, follow these steps:

Step 1: Set up Azure OpenAI

To configure Azure OpenAI, we’ll use the GPT-4O model for text and the DALL-E model for images. Set up the OpenAIClient as shown in the following code example.

internal class AzureOpenAIService
{
    const string endpoint = "https://{YOUR_END_POINT}.openai.azure.com";
    const string deploymentName = "GPT-4O";
    const string imageDeploymentName = "DALL-E";
    string key = "API key";
    
    OpenAIClient? client;
    ChatCompletionsOptions? chatCompletions;
    
    internal AzureOpenAIService()
    {
        
    }
}

Step 2: Connect to the Azure OpenAI

Let’s set up the connection to Azure OpenAI. Refer to the following code.

// At the time of required.
this.client = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(key)

This connection allows you to send prompts to the model and receive responses, which can be used to generate map markers for .NET MAUI Maps.

Step 3: Get the result from the AI service

Implement the GetResultsFromAI and GetImageFromAI methods to retrieve responses from the OpenAI API based on user input.

/// <summary>
/// Retrieves an answer from the deployment name model using the provided user prompt.
/// </summary>
/// <param name="userPrompt">The user prompt.</param>
/// <returns>The AI response.</returns>
public async Task<string> GetResultsFromAI(string userPrompt)
{
    if (this.Client != null && this.chatCompletions != null)
    {
        // Add the system message and user message to the options.
        this.chatCompletions.Messages.Add(new ChatRequestSystemMessage("You are a predictive analytics assistant."));
        this.chatCompletions.Messages.Add(new ChatRequestUserMessage(userPrompt));
        try
        {
            var response = await Client.GetChatCompletionsAsync(this.chatCompletions);
            return response.Value.Choices[0].Message.Content;
        }
        catch
        {
            return string.Empty;
        }
    }
    return string.Empty;
}

/// <summary>
/// Retrieves an image from the image deployment name model using the provided location name.
/// </summary>
/// <param name="locationName">The location name.</param>
/// <returns>The AI response.</returns>
public async Task<Uri> GetImageFromAI(string? locationName)
{
    var imageGenerations = await Client!.GetImageGenerationsAsync(
        new ImageGenerationOptions()
        {
            Prompt = $"Share the {locationName} image. If the image is not available share common image based on the location",
            Size = ImageSize.Size1024x1024,
            Quality = ImageGenerationQuality.Standard,
            DeploymentName = imageDeploymentName,
        });
        var imageUrl = imageGenerations.Value.Data[0].Url;
        return new Uri(imageUrl.ToString());
}

That’s it! The AzureOpenAIService class now offers a convenient way to interact with the OpenAI API and retrieve completion results based on the provided prompt.

Integrating AI-powered smart location search in .NET MAUI Autocomplete

Let’s design the AI-powered smart location search UI using the .NET MAUI Autocomplete control and then map the selected location into the .NET MAUI Maps control. Before proceeding, please refer to the getting started documentation for both Syncfusion .NET MAUI Maps and Autocomplete controls.

Step 1: Create a custom marker model

Create a custom marker model to define geographic location information for .NET MAUI Maps tile layer markers. The model can also include a name, details, address, and image to provide additional information for the marker tooltip.

/// <summary>
/// Represents information about a geographic location, including its name, details, coordinates, address and image.
/// </summary>
public class CustomMarker : MapMarker
{
     /// <summary>
     /// Gets or sets the name.
     /// </summary>
     public string? Name { get; set; }
       
     /// <summary>
     /// Gets or sets the details.
     /// </summary>
     public string? Details { get; set; }
     
     /// <summary>
     /// Gets or sets the image source.
     /// </summary>
     public Uri? Image { get; set; }
     
     /// <summary>
     /// Gets or sets the address.
     /// </summary>
     public string? Address { get; set; }
        
     /// <summary>
     /// Gets or sets the image name.
     /// </summary>
     public string? ImageName { get; set; }
}

Step 2: Add Maps tile layer in .NET MAUI Maps

Now, add a tile layer in the .NET MAUI Maps that can be used to search for and locate landmarks based on user input.

Refer to the following code example.

xmlns:maps="clr-namespace:Syncfusion.Maui.Maps;assembly=Syncfusion.Maui.Maps"

<maps:SfMaps x:Name="maps">
    <maps:SfMaps.Layer>
        <maps:MapTileLayer x:Name="layer"
                          UrlTemplate="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
                           CanCacheTiles="True"
                           ShowMarkerTooltip="True">
            <maps:MapTileLayer.Center>
                <maps:MapLatLng x:Name="mapLatLng"
                                Latitude="37.0902"
                                Longitude="-95.7129">
                </maps:MapLatLng>
            </maps:MapTileLayer.Center>
            
            <maps:MapTileLayer.ZoomPanBehavior>
               <maps:MapZoomPanBehavior x:Name="zoomPanBehavior"
                                          ZoomLevel="4"
                                          MinZoomLevel="4"
                                          MaxZoomLevel="18"
                                          EnableDoubleTapZooming="True" />
            </maps:MapTileLayer.ZoomPanBehavior>
        </maps:MapTileLayer>
   </maps:SfMaps.Layer>
</maps:SfMaps>

Step 3: Customizing the .NET MAUI Maps marker and tooltips

In this step, we’ll customize the .NET MAUI Maps markers and tooltips to effectively display relevant information, thereby improving the overall user experience on the map.

xmlns:maps="clr-namespace:Syncfusion.Maui.Maps;assembly=Syncfusion.Maui.Maps"

<Grid.Resources>
    <ResourceDictionary>
        <DataTemplate x:Key="MarkerTemplate">
            <StackLayout IsClippedToBounds="false"
                         HorizontalOptions="Start"
                         VerticalOptions="Start"
                         HeightRequest="30">
                <Image Source="map_pin.png"
                       Scale="1"
                       Aspect="AspectFit"
                       HorizontalOptions="Start"
                       VerticalOptions="Start"
                       HeightRequest="30"
                       WidthRequest="30" />
            </StackLayout>
        </DataTemplate>
      
        <DataTemplate x:Key="DetailTemplate">
            <Frame HasShadow="True" Margin="0" Padding="0" CornerRadius="10" WidthRequest="250">
                <StackLayout BackgroundColor="Transparent" Orientation="Vertical">
                    <Image Source="{Binding DataItem.Image}" HeightRequest="120" Margin="0" WidthRequest="250" Aspect="AspectFill"/>
                    <Label Grid.Row="1" Text="{Binding DataItem.Name}" FontAttributes="Bold" FontSize="12" LineBreakMode="WordWrap" Padding="10,5,0,0"/>
                    <Label Grid.Row="2" Text="{Binding DataItem.Details}" LineBreakMode="WordWrap" FontSize="10" Padding="10,0,0,0"/>
                    <Label Grid.Row="3" Padding="10,0,0,5">
                        <Label.FormattedText>
                            <FormattedString>
                                <Span Text="&#xe76e;" FontSize="8" FontFamily="MauiSampleFontIcon"/>
                                <Span Text="{Binding DataItem.Address}" FontSize="10"/>
                            </FormattedString>
                        </Label.FormattedText>
                    </Label>
                </StackLayout>
            </Frame>
        </DataTemplate>
      
        <DataTemplate x:Key="NormalTemplate">
            <Frame HasShadow="True" Margin="0" Padding="0" CornerRadius="10" WidthRequest="250">
                <StackLayout BackgroundColor="Transparent" Orientation="Vertical">
                    <Image Source="{Binding DataItem.Image}" HeightRequest="120" Margin="0" WidthRequest="250" Aspect="AspectFill"/>
                    <Label Grid.Row="1" Text="{Binding DataItem.Name}" FontAttributes="Bold" FontSize="12" LineBreakMode="WordWrap" Padding="10,5,0,0"/>
                    <Label Grid.Row="2" Text="{Binding DataItem.Details}" LineBreakMode="WordWrap" FontSize="10" Padding="10,0,0,5"/>
                </StackLayout>
            </Frame>
        </DataTemplate>
        
        <local:MarkerTemplateSelector x:Key="MarkerTemplateSelector"
                       DetailTemplate="{StaticResource DetailTemplate}"
                       NormalTemplate="{StaticResource NormalTemplate}"/>
    </ResourceDictionary>
</Grid.Resources>

<maps:SfMaps x:Name="maps">
    <maps:SfMaps.Layer>
        <maps:MapTileLayer x:Name="layer"
                          UrlTemplate="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
                           CanCacheTiles="True"
                           ShowMarkerTooltip="True"
                           MarkerTooltipTemplate="{StaticResource MarkerTemplateSelector}"
                           MarkerTemplate="{StaticResource MarkerTemplate}">
          <maps:MapTileLayer.MarkerTooltipSettings>
            <maps:MapTooltipSettings Background="Transparent"/>
          </maps:MapTileLayer.MarkerTooltipSettings>
        </maps:MapTileLayer>
    </maps:SfMaps.Layer>
</maps:SfMaps>

Refer to the following code example to select the marker tooltip data template.

public class MarkerTemplateSelector : DataTemplateSelector
{
        /// <summary>
        /// Gets or sets the normal template.
        /// </summary>
        public DataTemplate? NormalTemplate { get; set; }
        
        /// <summary>
        /// Gets or sets the detail template.
        /// </summary>
        public DataTemplate? DetailTemplate { get; set; }
        
        /// <summary>
        /// Selects a data template based on the properties of the given custom marker item.
        /// </summary>
        /// <param name="item">The custom marker item.</param>
        /// <param name="container">The bindable object.</param>
        /// <returns>Return suitable template.</returns>
        protected override DataTemplate? OnSelectTemplate(object item, BindableObject container)
        {
            var customMarker = (CustomMarker)item;
            return customMarker.Address == null ? NormalTemplate : DetailTemplate;
        }
}

Step 4: Integrating .NET MAUI Autocomplete in searching UI

Then, we’ll add the .NET MAUI Autocomplete control to collect the user input, which can then be passed to an AI service to retrieve geometric details.

Refer to the following code example to add the .NET MAUI Autocomplete control and design a search button.

xmlns:editors="clr-namespace:Syncfusion.Maui.Inputs;assembly=Syncfusion.Maui.Inputs"

<HorizontalStackLayout VerticalOptions="Start" IsClippedToBounds="False" HorizontalOptions="Start" WidthRequest="{OnPlatform Default=350, Android=300}" Margin="10" IsVisible="True">
    <!--Get location inputs from users to find a location-->
    <editors:SfAutocomplete x:Name="autoComplete"
                            IsClearButtonVisible="False"
                            HorizontalOptions="Start"
                            WidthRequest="{OnPlatform Default=350, Android=300}"
                            HeightRequest="50"
                            DropDownItemHeight="50" 
                            Text="Hospitals in New York">
    </editors:SfAutocomplete>
    
    <!--Location Search button -->
    <Button x:Name="button" Text=""
            Margin="-55,0,0,0"
            BackgroundColor="Transparent"
            BorderColor="Transparent"
            FontSize="20"
            TextColor="Black"
            FontFamily="MauiSampleFontIcon"
            HeightRequest="50"
            WidthRequest="50"/>
</HorizontalStackLayout>

Step 5: Enable AI-powered smart searching in .NET MAUI Maps

Finally, let’s add the prompt that requests the AI service to convert the user input into geographic locations in JSON format. The JSON data is then parsed into custom markers, which are added to the .NET MAUI Maps by using its Markers property in the MapTileLayer class.

/// <summary>
/// Method to contain AI response and updates.
/// </summary>
/// <param name="userQuery"></param>
/// <returns></returns>
private async Task GetRecommendationAsync(string userQuery)
{
    if (this.autoComplete == null || this.mapTileLayer == null || this.zoomPanBehavior == null)
    {
        return;
    }
    
    if (string.IsNullOrWhiteSpace(this.autoComplete.Text))
    {
        return;
    }
    
    if (this.busyIndicator != null)
    {
        this.busyIndicator.IsVisible = true;
        this.busyIndicator.IsRunning = true;
    }
     
    //Prompt that requests the AI service to convert the user input into geographic locations.
    string prompt = $"Given location name: {userQuery}" +
       $"\nSome conditions need to follow:" +
       $"\nCheck the location name is just a state, city, capital or region, then retrieve the following fields: location name, detail, latitude, longitude, and set address value as null" +
       $"\nOtherwise, retrieve minimum 5 to 6 entries with following fields: location's name, details, latitude, longitude, address." +
       $"\nThe return format should be the following JSON format: markercollections[Name, Details, Latitude, Longitude, Address]" +
       $"\nRemove ```json and remove ``` if it is there in the code." +
       $"\nProvide JSON format details only, No need any explanation.";

    var returnMessage = await azureAIHelper.GetResultsFromAI(prompt);
    var jsonObj = new JObject();
    jsonObj = JObject.Parse(returnMessage);

    this.customMarkers?.Clear();
    foreach (var marker in jsonObj["markercollections"])
    {
           CustomMarker customMarker = new CustomMarker();
           customMarker.Name = (string)marker["Name"];
           customMarker.Details = (string)marker["Details"];
           customMarker.Address = (string)marker["Address"];
           customMarker.Latitude = StringToDoubleConverter((string)marker["Latitude"]);
           customMarker.Longitude = StringToDoubleConverter((string)marker["Longitude"]);
           if (this.azureAIHelper.Client != null)
           {
               customMarker.Image = await azureAIHelper.GetImageFromAI(customMarker.Name);
               customMarker.ImageName = string.Empty;
           }
           //JSON data is then parsed into custom markers to add in .NET MAUI Maps.
           this.customMarkers?.Add(customMarker);
    }

    this.mapTileLayer.Markers = this.customMarkers;
    this.mapTileLayer.EnableCenterAnimation = true;
    if (this.customMarkers != null && this.customMarkers.Count > 0)
    {
         var firstMarker = this.customMarkers[0];
         this.mapTileLayer.Center = new MapLatLng
         {
             Latitude = firstMarker.Latitude,
             Longitude = firstMarker.Longitude,
         };

         if (this.azureAIHelper.Client != null)
         {
              this.zoomPanBehavior.ZoomLevel = 10;
         }
    }
}

Refer to the following output image.

Integrating AI-driven smart location search in .NET MAUI Maps

Supercharge your cross-platform apps with Syncfusion's robust .NET MAUI controls.

GitHub reference

For more details, refer to the AI-driven smart search in the .NET MAUI Maps GitHub demo.

Conclusion

Thanks for reading! In this blog, we’ve explored how to integrate AI-driven smart location search in the Syncfusion .NET MAUI Maps. This feature is available in the latest 2024 Volume 3 release. You can check out all the features in our Release Notes and What’s New pages.

Existing customers can download the latest version of Essential Studio from the License and Downloads page. If you are not yet a customer, you can try our 30-day free trial to check out these new features.

If you have questions, you can contact us through our support forumfeedback portal, or support portal. We are always happy to assist you!

Related blogs

Muniappan Subramanian

Muniappan S is a software engineer/.NET developer at Syncfusion. With a background in .NET development since 2017, he possesses expertise in MAUI, Xamarin.Forms, and Flutter platforms. Currently, he is focused on creating custom components for the MAUI platform.