Thanks for coming to my TechBash 2026 sessions!

Kalahari Resorts, Pocono Manor, Pennsylvania · October 13 to 16, 2026

Both demos are set in northeastern Pennsylvania, not far from the conference.

Everything from both sessions is on this page: the full slide decks, the sample applications, files you can download, and the reading list I promised from the stage. The slides open right in your browser. Use the arrow keys or Space to move through them, T for a thumbnail rail, and F for full screen.

Wednesday, October 14 · 1:30 PM · Rosewood · Data track

Spatial Data in .NET: From Database to Dashboard

A field-service dispatch board for a fictional company around Scranton answers one question: which service territory covers this customer, and which available technician assigned there is closest? The answer comes out of SQL Server geography columns through EF Core and NetTopologySuite, a Minimal API, and GeoJSON, with GeoBlazor drawing the map. My claim for the hour was that spatial is just a data type, and it composes with ordinary business data and ordinary LINQ.

The Spatial Dispatch board, recommending Jordan Alvarez for the urgent Summit Sports Catering job inside the North Valley territory

Thursday, October 15 · 11:30 AM · Sagewood

Real-Time Data Visualization in Blazor

A Blazor WebAssembly dispatcher dashboard tracks a simulated delivery fleet around the Poconos, using GeoBlazor Core for the map. The goal is live telemetry without drowning the browser in redraws: keep only the latest report per vehicle, hand the map one batch per cadence tick, and give every visual its own clock. All of the vehicle data is simulated, so the sample runs anywhere with no live feed behind it.

The Fleet dispatch dashboard showing live trucks on routes around Mount Pocono and Tannersville

Running the Samples

Both applications live in the dymaptic/GeoBlazor-Samples repository, and each project's README.md has the full setup. A few things worth knowing before you clone:

  • SpatialDispatch needs Docker for the SQL Server container (docker compose up -d from the project folder), then dotnet run --project SpatialDispatch. Startup applies the migration and seeds the demo records, so there's no manual database work. The map uses GeoBlazor Pro, so it needs a Pro license key in appsettings.Development.json. Without one, the dispatch answer, the candidate list, and assignment all still work, because the spatial work happens in SQL Server, not in the map.
  • FleetDashboard runs on a free GeoBlazor registration key. Its preview controls and the ?mode= URL parameter let you switch between the pipeline stages from the talk without rebuilding.

Spatial Data Downloads

These are the artifacts behind the query and indexing slides.

  • generated-spatial-queries.sql: the SQL that EF Core emits for the two dispatch queries, "which territory contains this job?" and "who is the closest available technician?"
  • territories.geojson: the two demo service territories. Paste it into geojson.io to see them.
  • Execution plans, which open as graphical plans in SQL Server Management Studio:
  • capture-execution-plan.ps1: rebuilds those plans yourself. It creates a throwaway SpatialDispatchPlan database with 200,000 technicians and 40,000 territories on the sample's SQL Server container, captures all four plans, and fails if a seek plan loses its spatial index. The row counts are hardcoded in its setup SQL, so edit them to match your own data before you trust the 45.3 km distance bound from the slides.

Spatial Data Resources

Packages

Documentation

  • EF Core spatial data is the single best starting point. It covers both providers, the geometry hierarchy, and which methods translate to SQL. Open it before you trust any translation.
  • SQL Server spatial data is the reference for the geometry and geography types and their methods.
  • SQL Server spatial indexes explains grid tessellation, the clustered primary key prerequisite, and GEOGRAPHY_AUTO_GRID.
  • NetTopologySuite API docs for method-level reference.
  • The PostGIS manual has a deeper function library than SQL Server's. It's worth reading even if you're on SQL Server, because the concepts are named the same way.
  • RFC 7946 (GeoJSON) is short, readable, and settles arguments about coordinate order. Longitude comes first!

If You're on PostgreSQL

  • Npgsql's NetTopologySuite mapping is the page behind the provider comparison slide: UseNpgsql(connectionString, o => o.UseNetTopologySuite()), the translation table (NTS Contains to ST_Contains, Distance to ST_Distance), and the trap that NTS types map to PostGIS geometry by default. Opt into geography per property with [Column(TypeName = "geography")] when you want distances in meters on the spheroid.
  • PostGIS geography covers when geography beats geometry and what it costs.
  • PostGIS GiST indexes replace SQL Server's grid tessellation, and they're a plain CREATE INDEX ... USING GIST (geom) with no hand-edited migration.
  • KNN nearest neighbor answers "closest" with the <-> operator (EF.Functions.DistanceKnn from the provider), which is a different mechanism from SQL Server's ORDER BY plus a distance filter.

If Someone Already Publishes the Data

  • ArcGIS Location Platform has a free tier for geocoding, routing, and hosted feature layers. The demo uses its routing service to compare drive time against straight-line distance.
  • A published feature layer is data your app reads over and over (boundaries, technician locations). Geocoding and routing are one-off calls per request. They have different cost models and different ways to fail.
  • GeoBlazor is the Blazor component library drawing the dashboard map. The open-source Core package covers MapView, GeoJSONLayer, GraphicsLayer, and events, and Pro adds things like the Sketch widget for drawing and editing territory boundaries. Disclosure: it's my library, built at dymaptic.

Where to Get Spatial Data

Almost everything above ships as a shapefile. ogr2ogr from GDAL is the tool that gets it into SQL Server or PostGIS, and it handles reprojection on the way in.

Geocoding

  • US Census Geocoder is free, needs no API key, and handles US addresses only. It's the fastest way to test an address-to-point pipeline without a contract.
  • Nominatim is free and global, but the public instance has a strict usage policy (one request per second, no bulk jobs). Run your own instance for anything serious.
  • ArcGIS geocoding is commercial and credit-based, on the same platform the demo uses for routing.

Whichever one you pick, store the result as geography with SRID 4326 and longitude first, and keep the geocoder's confidence score. A rooftop match and an interpolated match are not the same promise.

Tools

  • QGIS is a free desktop GIS, and the fastest way to look at spatial data before writing any code, or to sanity check what's actually in your table.
  • GDAL handles format conversion and reprojection, and it's available as a container if you don't want it installed.
  • The spatial results tab in SQL Server Management Studio is built in and surprisingly useful for "did my insert do what I think?" pgAdmin has a geometry viewer that does the same for PostGIS.
  • geojson.io lets you paste GeoJSON and see it on a map, which is great for debugging API output.
  • epsg.io looks up any SRID, with its units and area of use.

Testing Spatial Code

This wasn't in the talk, but it's worth writing down.

  • Anything that's pure NetTopologySuite is unit testable without a database: geometry construction, Contains, Intersects, Distance, Buffer, and validity checks. Build geometries from Well-Known Text in your fixtures. new WKTReader().Read("POLYGON((...))") is far more readable in a test than coordinate arrays.
  • Anything that depends on the database's spatial engine is not. SQL Server's geography distance and NTS's Distance() don't compute the same number, because one is ellipsoidal and the other is planar, so an assertion that passes in memory can fail against the database.
  • So integration test the queries, against a containerized SQL Server or PostGIS from Testcontainers for .NET, seeded with a small known fixture. Test that the query translates and returns the right rows, because that's the part that actually breaks.
  • Assert with a tolerance. Compare distances within a range, and choose EqualsTopologically or EqualsExact deliberately.
  • If a query calls an NTS method with no SQL translation, EF Core throws at runtime, not compile time. An integration test is the only thing that catches that before a user does.
  • Fixture geometries worth having: a simple square, two overlapping squares, a polygon with a hole, a self-intersecting invalid polygon, and a point exactly on a boundary edge. The last two find the most bugs.

Front-End Map Libraries

  • GeoBlazor wraps the ArcGIS Maps SDK for JavaScript in Blazor components. Again, mine, so weigh that accordingly.
  • Leaflet is small and stable, with an enormous plugin ecosystem, and the easiest JavaScript interop story from Blazor.
  • MapLibre GL JS renders vector tiles with WebGL, and it's the best-looking option for large datasets.
  • OpenLayers is the heaviest and most capable, with strong projection support if you're not in 4326.
  • ArcGIS Maps SDK for JavaScript is what GeoBlazor wraps, if you'd rather use it directly.

All of them consume GeoJSON, so the serialization work from the talk isn't tied to any one library.

Exercises

These extend something from the talk, easiest first.

  1. Break the happy path. Seed a customer outside every territory, one inside two territories, and one exactly on a shared border. Compare Contains, Intersects, Covers, and CoveredBy for all three, on both geography and geometry. The border case is where they disagree, and the two storage types can disagree with each other too.
  2. Port the demo to PostGIS. Swap UseSqlServer for UseNpgsql, opt the columns into geography, and re-run the containment and ranking queries. Check the generated SQL and the distance units before trusting it.
  3. Run the index investigation at your scale. Use capture-execution-plan.ps1 after editing its row counts to match your data. Note when the seek appears and how tight the distance bound has to be to keep it.
  4. Publish and consume. Push the demo territories to a hosted feature layer, then point a GeoBlazor layer (or plain fetch plus GeoJSON) at it. Note what changes about authentication.
  5. Sketch a territory. Add a GeoBlazor Pro Sketch widget that draws a boundary and posts the ring back to your API as Well-Known Text. Check the vertex order before storing it, because SQL Server reads a reversed ring as the whole globe minus your shape.
  6. Geocode an address end to end. Use the Census Geocoder for a US address, store the point, and run the dispatch query against it. Log the match confidence.
  7. Find the gaps and overlaps. Union the territory polygons, then Difference that against a county or state boundary to find uncovered ground, and Intersection pairs of territories to find double-covered ground. Decide which results are data errors and which are policy.
  8. Rank by drive time, not distance. Request routes for the three closest eligible technicians and order by travel time instead of STDistance. The nearest technician isn't always the fastest, and each route is a per-request cost the distance query doesn't have.
  9. Reproject before GeoJSON. Store or compute a projected geometry (Web Mercator, SRID 3857, for example), then reproject to 4326 before emitting RFC 7946 output. Confirm the map still lines up, and compare the distances in each form.
  10. Use spatial types with Dapper. Run one containment query through Dapper instead of EF Core. The reader still needs the NetTopologySuite plugin registered on the connection to turn a geography column into a Polygon.
  11. Diagnose an untranslatable expression. Write a query that calls an NTS method EF Core can't translate, watch it throw at runtime, and rewrite it into a shape the provider does translate.

Worth an Hour of Reading

  • Coordinate reference systems. Why 4326 isn't a projection, why 3857 distorts area at high latitudes, and why your distances came out in degrees.
  • Spatial indexing. R-trees (PostGIS GiST) and SQL Server's grid tessellation are different mechanisms with the same goal: cheaply rule out candidates before doing exact math.
  • Topological validity. Self-intersections and unclosed rings produce predicate results that look like bugs in your code. IsValid and Buffer(0) are the usual first aid.
  • Geometry simplification. Douglas-Peucker and its topology-preserving variants are necessary for any web map with real boundary data.
  • Payload size. The real cloud cost surprise with spatial data is egress on unsimplified geometry, not compute.

Real-Time Data Visualization Resources

Get in Touch

If you try either sample on your own data, I'd love to hear how it goes. Ping me on Mastodon at @TimPurdum@dotnet.social, or email me at tim.purdum@dymaptic.com if you get stuck!

Discover GeoBlazor

Interactive ArcGIS maps for your Blazor applications, with an open-source core.