|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: AI-Driven smart location search in .NET MAUI Maps control | Syncfusion |
| 4 | +description: Learn here all about the AI-Driven smart location searching feature of Syncfusion<sup>®</sup> .NET MAUI Maps (SfMaps) control and more. |
| 5 | +platform: MAUI |
| 6 | +control: SfMaps |
| 7 | +documentation: ug |
| 8 | +--- |
| 9 | + |
| 10 | +# AI-Driven Smart Location Search in .NET MAUI Maps (SfMaps) |
| 11 | + |
| 12 | +This document provides a comprehensive guide to implementing advanced search functionality within the Syncfusion [.NET MAUI Maps](https://help.syncfusion.com/cr/maui/Syncfusion.Maui.Maps.SfMaps.html) control. By integrating Azure OpenAI, this solution enables an intelligent, AI-powered location search experience. |
| 13 | + |
| 14 | +## Integrating Azure OpenAI with the .NET MAUI app |
| 15 | + |
| 16 | +First, open [Visual Studio](https://visualstudio.microsoft.com/) and [create a new .NET MAUI app](https://learn.microsoft.com/en-us/dotnet/maui/get-started/first-app?view=net-maui-7.0&tabs=vswin&pivots=devices-android). |
| 17 | + |
| 18 | +Before unlocking the power of AI to locate specific places effortlessly, ensure that you have access to [Azure OpenAI](https://azure.microsoft.com/en-in/products/ai-services/openai-service) and have set up a deployment in the Azure portal. You can find the [Azure.AI.OpenAI](https://www.nuget.org/packages/Azure.AI.OpenAI/1.0.0-beta.12) NuGet package in the [NuGet Gallery](https://www.nuget.org/) and install it in the project. |
| 19 | + |
| 20 | +Once you get your key and endpoint, follow these steps: |
| 21 | + |
| 22 | +### Step 1: Set up Azure OpenAI |
| 23 | + |
| 24 | +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. |
| 25 | + |
| 26 | +{% tabs %} |
| 27 | + |
| 28 | +{% highlight c# %} |
| 29 | + |
| 30 | +internal class AzureOpenAIService |
| 31 | +{ |
| 32 | + const string endpoint = "https://{YOUR_END_POINT}.openai.azure.com"; |
| 33 | + const string deploymentName = "GPT-4O"; |
| 34 | + const string imageDeploymentName = "DALL-E"; |
| 35 | + string key = "API key"; |
| 36 | + |
| 37 | + OpenAIClient? client; |
| 38 | + ChatCompletionsOptions? chatCompletions; |
| 39 | + |
| 40 | + internal AzureOpenAIService() |
| 41 | + { |
| 42 | + |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +{% endhighlight %} |
| 47 | + |
| 48 | +{% endtabs %} |
| 49 | + |
| 50 | +### Step 2: Connect to the Azure OpenAI |
| 51 | + |
| 52 | +To set up the connection to Azure OpenAI. Refer to the following code. |
| 53 | + |
| 54 | +{% tabs %} |
| 55 | + |
| 56 | +{% highlight c# %} |
| 57 | + |
| 58 | + // At the time of required. |
| 59 | + this.client = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(key) |
| 60 | + |
| 61 | +{% endhighlight %} |
| 62 | + |
| 63 | +{% endtabs %} |
| 64 | + |
| 65 | +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. |
| 66 | + |
| 67 | +### Step 3: Get the result from the AI service |
| 68 | + |
| 69 | +Implement the `GetResultsFromAI` and `GetImageFromAI` methods to retrieve responses from the OpenAI API based on user input. |
| 70 | + |
| 71 | +{% tabs %} |
| 72 | + |
| 73 | +{% highlight c# %} |
| 74 | + |
| 75 | +public async Task<string> GetResultsFromAI(string userPrompt) |
| 76 | +{ |
| 77 | + if (this.Client != null && this.chatCompletions != null) |
| 78 | + { |
| 79 | + // Add the system message and user message to the options. |
| 80 | + this.chatCompletions.Messages.Add(new ChatRequestSystemMessage("You are a predictive analytics assistant.")); |
| 81 | + this.chatCompletions.Messages.Add(new ChatRequestUserMessage(userPrompt)); |
| 82 | + try |
| 83 | + { |
| 84 | + var response = await Client.GetChatCompletionsAsync(this.chatCompletions); |
| 85 | + return response.Value.Choices[0].Message.Content; |
| 86 | + } |
| 87 | + catch |
| 88 | + { |
| 89 | + return string.Empty; |
| 90 | + } |
| 91 | + } |
| 92 | + return string.Empty; |
| 93 | +} |
| 94 | + |
| 95 | +public async Task<Uri> GetImageFromAI(string? locationName) |
| 96 | +{ |
| 97 | + var imageGenerations = await Client!.GetImageGenerationsAsync( |
| 98 | + new ImageGenerationOptions() |
| 99 | + { |
| 100 | + Prompt = $"Share the {locationName} image. If the image is not available share common image based on the location", |
| 101 | + Size = ImageSize.Size1024x1024, |
| 102 | + Quality = ImageGenerationQuality.Standard, |
| 103 | + DeploymentName = imageDeploymentName, |
| 104 | + }); |
| 105 | + var imageUrl = imageGenerations.Value.Data[0].Url; |
| 106 | + return new Uri(imageUrl.ToString()); |
| 107 | +} |
| 108 | + |
| 109 | +{% endhighlight %} |
| 110 | + |
| 111 | +{% endtabs %} |
| 112 | + |
| 113 | +The AzureOpenAIService class now offers a convenient way to interact with the OpenAI API and retrieve completion results based on the provided prompt. |
| 114 | + |
| 115 | +## Integrating AI-powered smart location search in .NET MAUI Autocomplete |
| 116 | + |
| 117 | +To design the AI-powered smart location search UI using the [.NET MAUI Autocomplete](https://www.syncfusion.com/maui-controls/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. |
| 118 | + |
| 119 | +### Step 1: Create a custom marker model |
| 120 | + |
| 121 | +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. |
| 122 | + |
| 123 | +{% tabs %} |
| 124 | + |
| 125 | +{% highlight c# %} |
| 126 | + |
| 127 | +public class CustomMarker : MapMarker |
| 128 | +{ |
| 129 | + public string? Name { get; set; } |
| 130 | + |
| 131 | + public string? Details { get; set; } |
| 132 | + |
| 133 | + public Uri? Image { get; set; } |
| 134 | + |
| 135 | + public string? Address { get; set; } |
| 136 | + |
| 137 | + public string? ImageName { get; set; } |
| 138 | +} |
| 139 | + |
| 140 | +{% endhighlight %} |
| 141 | + |
| 142 | +{% endtabs %} |
| 143 | + |
| 144 | +### Step 2: Add Maps tile layer in .NET MAUI Maps |
| 145 | + |
| 146 | +Now, add a [tile layer](https://help.syncfusion.com/maui/maps/getting-started#add-tile-layer) in the .NET MAUI Maps that can be used to search for and locate landmarks based on user input. |
| 147 | + |
| 148 | +{% tabs %} |
| 149 | + |
| 150 | +{% highlight xaml %} |
| 151 | + |
| 152 | +<maps:SfMaps x:Name="maps"> |
| 153 | + <maps:SfMaps.Layer> |
| 154 | + <maps:MapTileLayer x:Name="layer" |
| 155 | + UrlTemplate="https://tile.openstreetmap.org/{z}/{x}/{y}.png" |
| 156 | + CanCacheTiles="True" |
| 157 | + ShowMarkerTooltip="True"> |
| 158 | + <maps:MapTileLayer.Center> |
| 159 | + <maps:MapLatLng x:Name="mapLatLng" |
| 160 | + Latitude="37.0902" |
| 161 | + Longitude="-95.7129"> |
| 162 | + </maps:MapLatLng> |
| 163 | + </maps:MapTileLayer.Center> |
| 164 | + |
| 165 | + <maps:MapTileLayer.ZoomPanBehavior> |
| 166 | + <maps:MapZoomPanBehavior x:Name="zoomPanBehavior" |
| 167 | + ZoomLevel="4" |
| 168 | + MinZoomLevel="4" |
| 169 | + MaxZoomLevel="18" |
| 170 | + EnableDoubleTapZooming="True" /> |
| 171 | + </maps:MapTileLayer.ZoomPanBehavior> |
| 172 | + </maps:MapTileLayer> |
| 173 | + </maps:SfMaps.Layer> |
| 174 | +</maps:SfMaps> |
| 175 | + |
| 176 | +{% endhighlight %} |
| 177 | + |
| 178 | +{% endtabs %} |
| 179 | + |
| 180 | +### Step 3: Customizing the .NET MAUI Maps marker and tooltips |
| 181 | + |
| 182 | +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. |
| 183 | + |
| 184 | +{% tabs %} |
| 185 | + |
| 186 | +{% highlight xaml %} |
| 187 | + |
| 188 | +<Grid.Resources> |
| 189 | + <ResourceDictionary> |
| 190 | + <DataTemplate x:Key="MarkerTemplate"> |
| 191 | + <StackLayout IsClippedToBounds="false" |
| 192 | + HorizontalOptions="Start" |
| 193 | + VerticalOptions="Start" |
| 194 | + HeightRequest="30"> |
| 195 | + <Image Source="map_pin.png" |
| 196 | + Scale="1" |
| 197 | + Aspect="AspectFit" |
| 198 | + HorizontalOptions="Start" |
| 199 | + VerticalOptions="Start" |
| 200 | + HeightRequest="30" |
| 201 | + WidthRequest="30" /> |
| 202 | + </StackLayout> |
| 203 | + </DataTemplate> |
| 204 | + |
| 205 | + <DataTemplate x:Key="DetailTemplate"> |
| 206 | + <Frame HasShadow="True" Margin="0" Padding="0" CornerRadius="10" WidthRequest="250"> |
| 207 | + <StackLayout BackgroundColor="Transparent" Orientation="Vertical"> |
| 208 | + <Image Source="{Binding DataItem.Image}" HeightRequest="120" Margin="0" WidthRequest="250" Aspect="AspectFill"/> |
| 209 | + <Label Grid.Row="1" Text="{Binding DataItem.Name}" FontAttributes="Bold" FontSize="12" LineBreakMode="WordWrap" Padding="10,5,0,0"/> |
| 210 | + <Label Grid.Row="2" Text="{Binding DataItem.Details}" LineBreakMode="WordWrap" FontSize="10" Padding="10,0,0,0"/> |
| 211 | + <Label Grid.Row="3" Padding="10,0,0,5"> |
| 212 | + <Label.FormattedText> |
| 213 | + <FormattedString> |
| 214 | + <Span Text="" FontSize="8" FontFamily="MauiSampleFontIcon"/> |
| 215 | + <Span Text="{Binding DataItem.Address}" FontSize="10"/> |
| 216 | + </FormattedString> |
| 217 | + </Label.FormattedText> |
| 218 | + </Label> |
| 219 | + </StackLayout> |
| 220 | + </Frame> |
| 221 | + </DataTemplate> |
| 222 | + |
| 223 | + <DataTemplate x:Key="NormalTemplate"> |
| 224 | + <Frame HasShadow="True" Margin="0" Padding="0" CornerRadius="10" WidthRequest="250"> |
| 225 | + <StackLayout BackgroundColor="Transparent" Orientation="Vertical"> |
| 226 | + <Image Source="{Binding DataItem.Image}" HeightRequest="120" Margin="0" WidthRequest="250" Aspect="AspectFill"/> |
| 227 | + <Label Grid.Row="1" Text="{Binding DataItem.Name}" FontAttributes="Bold" FontSize="12" LineBreakMode="WordWrap" Padding="10,5,0,0"/> |
| 228 | + <Label Grid.Row="2" Text="{Binding DataItem.Details}" LineBreakMode="WordWrap" FontSize="10" Padding="10,0,0,5"/> |
| 229 | + </StackLayout> |
| 230 | + </Frame> |
| 231 | + </DataTemplate> |
| 232 | + |
| 233 | + <local:MarkerTemplateSelector x:Key="MarkerTemplateSelector" |
| 234 | + DetailTemplate="{StaticResource DetailTemplate}" |
| 235 | + NormalTemplate="{StaticResource NormalTemplate}"/> |
| 236 | + </ResourceDictionary> |
| 237 | +</Grid.Resources> |
| 238 | + |
| 239 | +<maps:SfMaps x:Name="maps"> |
| 240 | + <maps:SfMaps.Layer> |
| 241 | + <maps:MapTileLayer x:Name="layer" |
| 242 | + UrlTemplate="https://tile.openstreetmap.org/{z}/{x}/{y}.png" |
| 243 | + CanCacheTiles="True" |
| 244 | + ShowMarkerTooltip="True" |
| 245 | + MarkerTooltipTemplate="{StaticResource MarkerTemplateSelector}" |
| 246 | + MarkerTemplate="{StaticResource MarkerTemplate}"> |
| 247 | + <maps:MapTileLayer.MarkerTooltipSettings> |
| 248 | + <maps:MapTooltipSettings Background="Transparent"/> |
| 249 | + </maps:MapTileLayer.MarkerTooltipSettings> |
| 250 | + </maps:MapTileLayer> |
| 251 | + </maps:SfMaps.Layer> |
| 252 | +</maps:SfMaps> |
| 253 | + |
| 254 | +{% endhighlight %} |
| 255 | + |
| 256 | +{% endtabs %} |
| 257 | + |
| 258 | +Refer to the following code example to select the marker tooltip data template. |
| 259 | + |
| 260 | +{% tabs %} |
| 261 | + |
| 262 | +{% highlight c# %} |
| 263 | + |
| 264 | +public class MarkerTemplateSelector : DataTemplateSelector |
| 265 | +{ |
| 266 | + public DataTemplate? NormalTemplate { get; set; } |
| 267 | + public DataTemplate? DetailTemplate { get; set; } |
| 268 | + |
| 269 | + protected override DataTemplate? OnSelectTemplate(object item, BindableObject container) |
| 270 | + { |
| 271 | + var customMarker = (CustomMarker)item; |
| 272 | + return customMarker.Address == null ? NormalTemplate : DetailTemplate; |
| 273 | + } |
| 274 | +} |
| 275 | + |
| 276 | +{% endhighlight %} |
| 277 | + |
| 278 | +{% endtabs %} |
| 279 | + |
| 280 | +### Step 4: Integrating .NET MAUI Autocomplete in searching UI |
| 281 | + |
| 282 | +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. |
| 283 | + |
| 284 | +Refer to the following code example to add the .NET MAUI Autocomplete control and design a search button. |
| 285 | + |
| 286 | +{% tabs %} |
| 287 | + |
| 288 | +{% highlight xaml %} |
| 289 | + |
| 290 | +<HorizontalStackLayout VerticalOptions="Start" IsClippedToBounds="False" HorizontalOptions="Start" WidthRequest="{OnPlatform Default=350, Android=300}" Margin="10" IsVisible="True"> |
| 291 | + <!--Get location inputs from users to find a location--> |
| 292 | + <editors:SfAutocomplete x:Name="autoComplete" |
| 293 | + IsClearButtonVisible="False" |
| 294 | + HorizontalOptions="Start" |
| 295 | + WidthRequest="{OnPlatform Default=350, Android=300}" |
| 296 | + HeightRequest="50" |
| 297 | + DropDownItemHeight="50" |
| 298 | + Text="Hospitals in New York"> |
| 299 | + </editors:SfAutocomplete> |
| 300 | + |
| 301 | + <!--Location Search button --> |
| 302 | + <Button x:Name="button" Text="" |
| 303 | + Margin="-55,0,0,0" |
| 304 | + BackgroundColor="Transparent" |
| 305 | + BorderColor="Transparent" |
| 306 | + FontSize="20" |
| 307 | + TextColor="Black" |
| 308 | + FontFamily="MauiSampleFontIcon" |
| 309 | + HeightRequest="50" |
| 310 | + WidthRequest="50"/> |
| 311 | +</HorizontalStackLayout> |
| 312 | + |
| 313 | +{% endhighlight %} |
| 314 | + |
| 315 | +{% endtabs %} |
| 316 | + |
| 317 | +### Step 5: Enable AI-powered smart searching in .NET MAUI Maps |
| 318 | + |
| 319 | +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. |
| 320 | + |
| 321 | +{% tabs %} |
| 322 | + |
| 323 | +{% highlight c# %} |
| 324 | + |
| 325 | +private async Task GetRecommendationAsync(string userQuery) |
| 326 | +{ |
| 327 | + if (this.autoComplete == null || this.mapTileLayer == null || this.zoomPanBehavior == null) |
| 328 | + { |
| 329 | + return; |
| 330 | + } |
| 331 | + |
| 332 | + if (string.IsNullOrWhiteSpace(this.autoComplete.Text)) |
| 333 | + { |
| 334 | + return; |
| 335 | + } |
| 336 | + |
| 337 | + if (this.busyIndicator != null) |
| 338 | + { |
| 339 | + this.busyIndicator.IsVisible = true; |
| 340 | + this.busyIndicator.IsRunning = true; |
| 341 | + } |
| 342 | + |
| 343 | + //Prompt that requests the AI service to convert the user input into geographic locations. |
| 344 | + string prompt = $"Given location name: {userQuery}" + |
| 345 | + $"\nSome conditions need to follow:" + |
| 346 | + $"\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" + |
| 347 | + $"\nOtherwise, retrieve minimum 5 to 6 entries with following fields: location's name, details, latitude, longitude, address." + |
| 348 | + $"\nThe return format should be the following JSON format: markercollections[Name, Details, Latitude, Longitude, Address]" + |
| 349 | + $"\nRemove ```json and remove ``` if it is there in the code." + |
| 350 | + $"\nProvide JSON format details only, No need any explanation."; |
| 351 | + |
| 352 | + var returnMessage = await azureAIHelper.GetResultsFromAI(prompt); |
| 353 | + var jsonObj = new JObject(); |
| 354 | + jsonObj = JObject.Parse(returnMessage); |
| 355 | + |
| 356 | + this.customMarkers?.Clear(); |
| 357 | + foreach (var marker in jsonObj["markercollections"]) |
| 358 | + { |
| 359 | + CustomMarker customMarker = new CustomMarker(); |
| 360 | + customMarker.Name = (string)marker["Name"]; |
| 361 | + customMarker.Details = (string)marker["Details"]; |
| 362 | + customMarker.Address = (string)marker["Address"]; |
| 363 | + customMarker.Latitude = StringToDoubleConverter((string)marker["Latitude"]); |
| 364 | + customMarker.Longitude = StringToDoubleConverter((string)marker["Longitude"]); |
| 365 | + if (this.azureAIHelper.Client != null) |
| 366 | + { |
| 367 | + customMarker.Image = await azureAIHelper.GetImageFromAI(customMarker.Name); |
| 368 | + customMarker.ImageName = string.Empty; |
| 369 | + } |
| 370 | + //JSON data is then parsed into custom markers to add in .NET MAUI Maps. |
| 371 | + this.customMarkers?.Add(customMarker); |
| 372 | + } |
| 373 | + |
| 374 | + this.mapTileLayer.Markers = this.customMarkers; |
| 375 | + this.mapTileLayer.EnableCenterAnimation = true; |
| 376 | + if (this.customMarkers != null && this.customMarkers.Count > 0) |
| 377 | + { |
| 378 | + var firstMarker = this.customMarkers[0]; |
| 379 | + this.mapTileLayer.Center = new MapLatLng |
| 380 | + { |
| 381 | + Latitude = firstMarker.Latitude, |
| 382 | + Longitude = firstMarker.Longitude, |
| 383 | + }; |
| 384 | + |
| 385 | + if (this.azureAIHelper.Client != null) |
| 386 | + { |
| 387 | + this.zoomPanBehavior.ZoomLevel = 10; |
| 388 | + } |
| 389 | + } |
| 390 | +} |
| 391 | + |
| 392 | +{% endhighlight %} |
| 393 | + |
| 394 | +{% endtabs %} |
| 395 | + |
| 396 | +You can find the complete sample from this [link](https://github.com/SyncfusionExamples/Integrating-AI-Driven-Location-Search-into-.NET-MAUI-Maps). |
0 commit comments