Documentation

fsds/spatial-azure-maps-geocoding-integration.md

Azure Maps Geocoding Integration for Spatial Component

Summary

Add geocoding functionality to the Spatial component using Azure Maps SDK to:

  1. Auto-geocode addresses on creation (fire-and-forget, fail silently)
  2. Manually geocode a specific address via API
  3. Batch geocode all addresses without coordinates (max 100 per request)

Key Decisions

  • SDK: Azure.Maps.Search NuGet package with MapsSearchClient
  • Batch Limit: 100 addresses per API call
  • Storage: Update BOTH Address.Latitude/Longitude AND create AzureAddress record
  • Config: Key Vault for production, appsettings.json fallback for local dev

Files to Create

1. Configuration Class

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial/Configuration/AzureMapsConfiguration.cs

  • Enabled (bool) - master switch
  • SubscriptionKey (string) - Azure Maps API key
  • BatchSize (int, default 100)
  • AutoGeocodeOnCreate (bool, default true)

2. Service Interface

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial.Abstractions/Services/IGeocodingService.cs

  • GeocodeAddressAsync(Guid addressId) - single address
  • GeocodeUnprocessedAddressesAsync(int? limit) - batch
  • GeocodeAddressInBackground(Guid addressId, Guid tenantId) - fire-and-forget

3. Response DTOs

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial.Abstractions/Responses/GeocodingResult.cs

  • GeocodingResult - single result with AddressId, Success, Lat/Long, Score, ErrorMessage
  • BatchGeocodingResult - TotalRequested, Successful, Failed, Results list

4. Service Implementation

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial/Services/AzureMapsGeocodingService.cs

  • Inject SpatialDb, IOptions<AzureMapsConfiguration>, ILogger, IServiceProvider
  • Create MapsSearchClient with AzureKeyCredential
  • Build query strings from address components (Line1, City, State, PostalCode, Country)
  • Map Azure response to both Address (lat/long) and AzureAddress (full details)
  • Fire-and-forget uses Task.Run with new scope (like Tn360SyncBackgroundService)

5. API Endpoints

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial/ApiEngines/GeocodingApi.cs

  • POST /geocoding/address/{addressId:guid} - geocode single address
  • POST /geocoding/batch?limit=N - batch geocode unprocessed addresses

Files to Modify

1. Directory.Packages.props (repo root)

Add: <PackageVersion Include="Azure.Maps.Search" Version="2.0.0-beta.5" />

2. Acsis.Dynaplex.Engines.Spatial.csproj

Add: <PackageReference Include="Azure.Maps.Search" />

3. Program.cs

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial/Program.cs

After line ~67 (service registrations):

// Azure Maps Geocoding
var azureMapsConfig = builder.Configuration.GetSection(AzureMapsConfiguration.SECTION_NAME);
if (azureMapsConfig.Exists() && azureMapsConfig.Get<AzureMapsConfiguration>()?.Enabled == true)
{
    builder.Services.Configure<AzureMapsConfiguration>(azureMapsConfig);
    builder.Services.AddScoped<IGeocodingService, AzureMapsGeocodingService>();
}

In MapAcsisEndpoints call (~line 82):
Add GeocodingApi.MapGeocodingEndpoints

4. appsettings.Development.json

engines/spatial/src/Acsis.Dynaplex.Engines.Spatial/appsettings.Development.json

Add:

"AzureMaps": {
  "Enabled": true,
  "SubscriptionKey": "your-dev-key",
  "BatchSize": 100,
  "AutoGeocodeOnCreate": true
}

Auto-Geocoding Integration Point

When address creation is implemented (currently commented out in LocationsApi.cs), add after SaveChangesAsync:

geocodingService.GeocodeAddressInBackground(address.Id, address.TenantId);

The GeocodeAddressInBackground method:

  • Checks if geocoding is enabled
  • Creates new DI scope (avoids DbContext lifetime issues)
  • Uses _ = Task.Run(...) fire-and-forget pattern
  • Catches all exceptions, logs warning, never throws

Error Handling Strategy

Scenario Behavior
Geocoding disabled Return early with message
Address not found Return error result, log warning
Azure Maps API error Return error result, log error
No results from API Return error result, log info
Auto-geocode failure Log warning only (never crash)
Batch item failure Mark item failed, continue others

Implementation Stages

Stage 1: Package and Configuration

  • Add NuGet package to Directory.Packages.props
  • Add package reference to Spatial.csproj
  • Create AzureMapsConfiguration.cs
  • Add config section to appsettings.Development.json

Stage 2: DTOs and Interface

  • Create GeocodingResult.cs and BatchGeocodingResult.cs
  • Create IGeocodingService.cs interface

Stage 3: Service Implementation

  • Create AzureMapsGeocodingService.cs
  • Implement single geocoding with Address + AzureAddress update
  • Implement batch geocoding using GetGeocodingBatch
  • Implement fire-and-forget background method

Stage 4: API Endpoints

  • Create GeocodingApi.cs with both endpoints
  • Register in Program.cs

Stage 5: Integration Testing

  • Test single address geocoding
  • Test batch geocoding
  • Verify both Address and AzureAddress tables updated

Critical File Paths

  • engines/spatial/src/Acsis.Dynaplex.Engines.Spatial/Program.cs - service registration
  • engines/spatial/src/Acsis.Dynaplex.Engines.Spatial.Database/Address.cs - entity with Lat/Long
  • engines/spatial/src/Acsis.Dynaplex.Engines.Spatial.Database/AzureAddress.cs - full geocoding details
  • engines/iot/src/Acsis.Dynaplex.Engines.Iot/Program.cs:61-70 - conditional registration pattern
  • engines/iot/src/Acsis.Dynaplex.Engines.Iot/Integrations/Emqx/EmqxApiClient.cs - external API pattern