Reverse Geocoding – Technical Details

Back to Demo

High-Level System Overview

  • Purpose: Resolve WGS84 latitude/longitude input into structured address and place metadata using ArcGIS reverseGeocode.
  • Business problem: Demonstrates low-latency geospatial lookup suitable for map-click workflows, field operations, asset lookup, and location validation.
  • Architecture: Razor Page and ArcGIS MapView → versioned ASP.NET Core API → IReverseGeocodingService → grid-snapped distributed cache → ArcGIS REST API.
  • Technologies: .NET 10, Razor Pages, ArcGIS JavaScript SDK, typed HttpClient, Polly resilience, IDistributedCache, Redis or in-memory fallback.
  • Deployment model: Runs inside the Portfolio web application on Render and can scale horizontally with Redis sharing cached coordinate results across replicas.

Frontend Architecture

The front end uses a Razor Page with wwwroot/js/ReverseGeocoding/app.js and ArcGIS JavaScript SDK map interactions. State is intentionally lightweight: the selected coordinate, current lookup result, and recent lookup history are managed in browser JavaScript.

  • Map interaction: Map click events provide coordinates that are sent to the backend API; users can also enter coordinates manually.
  • API communication: api-config.js defines /api/v1/geocoding/reverse, and shared fetch helpers provide consistent cookie and error behavior.
  • State management: Lookup history is page-local, avoiding server persistence for demo interactions that do not require identity.
  • UX decisions: The page separates raw coordinate input from structured returned address components, making geocoding accuracy and location type easy to discuss.

API Layer

  • Controller: ReverseGeocodingController is versioned at /api/v1/geocoding/reverse and is [AllowAnonymous].
  • Endpoint: GET /api/v1/geocoding/reverse?lat={lat}&lng={lng} returns ReverseGeocodingResultDto.
  • Validation: The service validates latitude in −90..90 and longitude in −180..180, throwing ArgumentException for invalid coordinates.
  • Error handling: Bad coordinates return 400, no usable ArcGIS address returns 404, and an open circuit breaker returns 503 with retry guidance.
  • Security: The endpoint is read-only, accepts numeric query values only, and does not require or mutate user identity.

Services and Business Logic

  • Service boundary: ReverseGeocodingService owns coordinate validation, grid snapping, cache key creation, ArcGIS request construction, response handling, and DTO mapping.
  • Geospatial optimization: Coordinates are snapped to ReverseGeocoding:GridResolutionDegrees before cache lookup so nearby clicks reuse a single cached result.
  • External integration: A typed HttpClient calls ArcGIS reverseGeocode through timeout, retry, and circuit breaker policies.
  • Mapping: ArcGIS wire models live in Portfolio.Common/ArcGis; MapToDto prefers StAddr with Address fallback because ArcGIS field population varies by address type.
  • Service contract: Public service methods accept CancellationToken, return DTOs, and throw typed exceptions for controller translation.

Data Access Layer

Reverse geocoding does not require relational persistence. Its state is cache-oriented because the expensive operation is an upstream geospatial lookup, not an internal database query.

  • Caching strategy: Results are serialized with shared JSON options into IDistributedCache using a sliding expiration configured by ReverseGeocoding:CacheSlidingExpirationMinutes.
  • Scale-out behavior: Redis lets multiple app replicas share snapped-coordinate cache entries; local development falls back to MemoryDistributedCache when Redis:ConnectionString is empty.
  • Query optimization: Grid snapping trades exact per-click precision for dramatically better cache hit rates in dense click workflows.
  • Database role: PostgreSQL remains available for other domains, but this workflow avoids unnecessary writes for ephemeral lookup results.

Infrastructure and Deployment

  • Hosting: Deployed with the main Portfolio application on Render; configuration is provided through appsettings and environment variables.
  • Distributed cache: Redis__ConnectionString switches the app from in-process cache to Redis-backed IDistributedCache for reverse-geocode results and other shared geocoding workflows.
  • Shared platform role: The same Redis instance also supports batch geocoding job state and production Data Protection keys for multi-replica cookie validity.
  • Reliability: Polly protects the app and ArcGIS dependency from long waits, transient failures, and repeated calls during upstream instability.
  • Containers: Docker Compose includes Redis for local cache behavior close to production; Kubernetes manifests support replica scaling behind a service.
  • Cloud architecture: In Azure, this maps cleanly to Azure Container Apps or AKS plus Azure Cache for Redis and managed PostgreSQL while preserving the current service API.

Engineering Decisions and Tradeoffs

  • Grid-snapped cache: Improves latency and reduces ArcGIS calls, but may return a nearby address instead of a unique result for every raw coordinate.
  • Anonymous access: Reduces friction for a public geospatial demo, but production usage would typically add quotas, API keys, or authenticated tenant limits.
  • No persistence: Keeps the workflow stateless and scalable, but removes historical lookup analytics unless observability or audit storage is added.
  • Future improvements: Add rate limiting, precision tiers, cache warming for known assets, telemetry on cache hit ratio, and region-aware upstream failover.

Interview Discussion Points

  • How would you choose grid resolution for urban, rural, and parcel-level workflows?
  • What metrics would prove the cache is reducing upstream geocoding cost?
  • How should a public reverse-geocoding API enforce fair usage without hurting UX?
  • Where would you add distributed tracing across browser, API, cache, and ArcGIS?
  • How would this design change if reverse geocoding were part of a high-volume mobile fleet system?