This article shows how to implement a geo location search in an ASP.NET Core application using a LeafletJs map. The selected location can be used to find the nearest location with an Elasticsearch Geo-distance query. The Elasticsearch container and the ASP.NET Core UI application are setup for development using .NET Aspire.
Code: https://github.com/damienbod/WebGeoElasticsearch
Setup
For local development, .NET Aspire is used to setup the two services and the HTTPS connections between the services. The services are configured in the Aspire AppHost project .

The Elasticsearch client is setup as a singleton and requires the connection configuration. This can be changed, if for example an API key is used instead. The connection URL is read from the configuration as well as the secrets.
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;
namespace WebGeoElasticsearch.ElasticsearchApi;
public class ElasticClientProvider
{
private readonly ElasticsearchClient? _client = null;
public ElasticClientProvider(IConfiguration configuration)
{
if (_client == null)
{
var settings = new ElasticsearchClientSettings(new Uri(configuration["ElasticsearchUrl"]!))
.Authentication(new BasicAuthentication(configuration["ElasticsearchUserName"]!,
configuration["ElasticsearchPassword"]!));
_client = new ElasticsearchClient(settings);
}
}
public ElasticsearchClient GetClient()
{
if (_client != null)
{
return _client;
}
throw new Exception("Elasticsearch client not initialized");
}
}
Create Index with mapping
The index cannot be created by adding a document because the mapping is created incorrectly using the default settings. The mapping can be created for the defined index using the Mappings extension from the Elastic.Clients.Elasticsearch Nuget package. This was added to the client project in the Aspire.Elastic.Clients.Elasticsearch package. The mapping is really simple and probably not complete for a production index, some keyword optimizations are required. The detailsCoordinates field is defined as a GeoPointProperty.
var mapping = await _client.Indices.CreateAsync<MapDetail>(IndexName, c => c
.Mappings(map => map
.Properties(
new Properties<MapDetail>()
{
{ "details", new TextProperty() },
{ "detailsCoordinates", new GeoPointProperty() },
{ "detailsType", new TextProperty() },
{ "id", new TextProperty() },
{ "information", new TextProperty() },
{ "name", new TextProperty() }
}
)
)
);
The created mapping can be validated using the “IndexName”/_mapping GET request. This returns the definitions as a Json response.
https://localhost:9200/mapdetails/_mapping
Documents can be added to the Elasticsearch index using the IndexAsync method.
response = await _client.IndexAsync(dotNetGroup, IndexName, "1");
Search Query
A Geo-distance query is used to find the distance from the selected location to the different Geo points in the index. This using latitude and longitude coordinates.
public async Task<List<MapDetail>> SearchForClosestAsync(
uint maxDistanceInMeter,
double centerLatitude,
double centerLongitude)
{
// Bern Lat 46.94792, Long 7.44461
if (maxDistanceInMeter == 0)
{
maxDistanceInMeter = 1000000;
}
var searchRequest = new SearchRequest(IndexName)
{
Query = new GeoDistanceQuery
{
DistanceType = GeoDistanceType.Plane,
Field = "detailsCoordinates",
Distance = $"{maxDistanceInMeter}m",
Location = GeoLocation.LatitudeLongitude(
new LatLonGeoLocation
{
Lat = centerLatitude,
Lon = centerLongitude
})
},
Sort = BuildGeoDistanceSort(centerLatitude, centerLongitude)
};
searchRequest.ErrorTrace = true;
_logger.LogInformation("SearchForClosestAsync: {SearchBody}",
searchRequest);
var searchResponse = await _client
.SearchAsync<MapDetail>(searchRequest);
return searchResponse.Documents.ToList();
}
The found results are returned sorted using the Geo-distance sort. This puts the location with the smallest distance first. This is used for the map display.
private static List<SortOptions> BuildGeoDistanceSort(
double centerLatitude,
double centerLongitude)
{
var sorts = new List<SortOptions>();
var sort = SortOptions.GeoDistance(
new GeoDistanceSort
{
Field = new Field("detailsCoordinates"),
Location = new List<GeoLocation>
{
GeoLocation.LatitudeLongitude(
new LatLonGeoLocation
{
Lat = centerLatitude,
Lon = centerLongitude
})
},
Order = SortOrder.Asc,
Unit = DistanceUnit.Meters
}
);
sorts.Add(sort);
return sorts;
}
Display using Leaflet.js
The ASP.NET Core displays the locations and the results of the search in a Leafletjs map component. The location closest to the center location is displayed differently. You can click around the map and test the different searches. The data used for this display is powered using the Geo-distance query.

Testing
The applications can be started using the .NET Aspire host project. One is run as a container, the other is a project. The docker container requires a Desktop docker installation on the host operating system. When the applications started, the containers need to boot up first. An optimization would remove this boot up.

Notes
Using Elasticsearch, it is very simple to create fairly complex search requests for your web applications. With a bit of experience complex reports, queries can be implemented as well. You can also use Elasticsearch aggregations to group and organize results for data analysis tools, reports. .NET Aspire makes it easy to develop locally and use HTTPS everywhere.
Links
https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-point.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/explicit-mapping.html