Tutorial: Create geospatial visualizations

Applies to: ✅ Azure Data ExplorerAzure MonitorMicrosoft Sentinel

Use the Kusto Query Language (KQL) to create geospatial visualizations. Geospatial clustering organizes data by location. KQL provides multiple geospatial clustering methods and geospatial visualization tools.

In this tutorial, you learn how to:

Prerequisites

To run the queries, you need a query environment that has access to the sample data. Use one of the following:

  • Microsoft account or Microsoft Entra user identity to sign in to the help cluster
  • Microsoft account or Microsoft Entra user identity to sign in to the help cluster

Use project to select the longitude column, then the latitude column. Use render to show the points on a map (scatter chart with kind set to map).

Plot points on a map

Use project to select the longitude column, then the latitude column. Use render to show the points on a map (scatter chart with kind set to map).

StormEvents
| take 100
| project BeginLon, BeginLat
| render scatterchart with (kind = map)

Screenshot of sample storm events on a map.

Plot multiple series of points

To visualize multiple point series, use project to select the longitude, latitude, and a third column that defines the series.

In the following query, the series is EventType. The points use different colors by EventType and, when selected, display the EventType value.

StormEvents
| take 100
| project BeginLon, BeginLat, EventType
| render scatterchart with (kind = map)

Screenshot of sample storm events on a map by type.

When the result has more columns than the longitude, latitude, and series columns, you can also explicitly specify the xcolumn (longitude), ycolumn (latitude), and series in the render operator.

StormEvents
| take 100
| render scatterchart with (kind = map, xcolumn = BeginLon, ycolumns = BeginLat, series = EventType)

Use GeoJSON values to plot points on a map

Dynamic GeoJSON values update frequently and are used in real-time mapping. Mapping points with dynamic GeoJSON values gives you flexibility and control that plain latitude and longitude can't provide.

The following query uses the geo_point_to_s2cell and geo_s2cell_to_central_point functions to map storm events on a scatter chart.

StormEvents
| project BeginLon, BeginLat
| summarize by hash=geo_point_to_s2cell(BeginLon, BeginLat, 5)
| project point = geo_s2cell_to_central_point(hash)
| project lng = toreal(point.coordinates[0]), lat = toreal(point.coordinates[1])
| render scatterchart with (kind = map)

Screenshot of sample storm events displayed using GeoJSON.

Represent data points with variable sized bubbles

Visualize data distribution by aggregating each cluster and plotting its central point.

For example, the following query filters storm events where EventType is Tornado. It groups events into longitude and latitude clusters, counts events in each cluster, projects each cluster's central point, and renders a map. Regions with the most tornadoes stand out by their larger bubble size.

StormEvents
| where EventType == "Tornado"
| project BeginLon, BeginLat
| where isnotnull(BeginLat) and isnotnull(BeginLon)
| summarize count_summary=count() by hash = geo_point_to_s2cell(BeginLon, BeginLat, 4)
| project geo_s2cell_to_central_point(hash), count_summary
| extend Events = "count"
| render piechart with (kind = map)

Screenshot of Azure Data Explorer showing a geospatial map of tornado events.

Display points within a specific area

Use a polygon to define the region and the geo_point_in_polygon function to filter for events that occur within that region.

The following query defines a polygon representing the southern California region and filters for storm events within this region. It then groups the events into clusters, counts the number of events in each cluster, projects the central point of the cluster, and renders a map to visualize the clusters.

let southern_california = dynamic({
    "type": "Polygon",
    "coordinates": [[[-119.5, 34.5], [-115.5, 34.5], [-115.5, 32.5], [-119.5, 32.5], [-119.5, 34.5]]
    ]});
StormEvents
| where geo_point_in_polygon(BeginLon, BeginLat, southern_california)
| project BeginLon, BeginLat
| summarize count_summary = count() by hash = geo_point_to_s2cell(BeginLon, BeginLat, 8)
| project geo_s2cell_to_central_point(hash), count_summary
| extend Events = "count"
| render piechart with (kind = map)

Screenshot of Azure Data Explorer web UI showing a geospatial map of southern California storms.

Show nearby points on a LineString

The following query finds nearby storm events that occur along a specified LineString, which represents a defined path. In this case, the LineString is a road to Key West. The geo_distance_point_to_line() function is used to filter the storm events based on their proximity to the defined LineString. If an event is within 500 meters from LineString, the event is rendered on a map.

let roadToKeyWest = dynamic({
"type":"linestring",
"coordinates":[
          [
            -81.79595947265625,
            24.56461038017685
          ],
          [
            -81.595458984375,
            24.627044746156027
          ],
          [
            -81.52130126953125,
            24.666986385216273
          ],
          [
            -81.35650634765625,
            24.66449040712424
          ],
          [
            -81.32354736328125,
            24.647017162630366
          ],
          [
            -80.8099365234375,
            24.821639356846607
          ],
          [
            -80.62042236328125,
            24.93127614538456
          ],
          [
            -80.37872314453125,
            25.175116531621764
          ],
          [
            -80.42266845703124,
            25.19251511519153
          ],
          [
            -80.4803466796875,
            25.46063471847754
          ]
        ]});
StormEvents
| where isnotempty(BeginLat) and isnotempty(BeginLon)
| project BeginLon, BeginLat, EventType
| where geo_distance_point_to_line(BeginLon, BeginLat, roadToKeyWest) < 500
| render scatterchart with (kind=map)

Screenshot of the result of the previous KQL query to calculate events along a LineString.

Show nearby points in a polygon

The following query finds nearby storm events that occur within a specified polygon. In this case, the polygon is a road to Key West. The geo_distance_point_to_polygon() function is used to filter the storm events based on their proximity to the defined polygon. If an event is within 500 meters of the polygon, the event is rendered on a map.

let roadToKeyWest = dynamic({
"type":"polygon",
"coordinates":[
          [
            [
              -80.08209228515625,
              25.39117928167583
            ],
            [
              -80.4913330078125,
              25.517657429994035
            ],
            [
              -80.57922363281249,
              25.477992320574817
            ],
            [
              -82.188720703125,
              24.632038149596895
            ],
            [
              -82.1942138671875,
              24.53712939907993
            ],
            [
              -82.13104248046875,
              24.412140070651528
            ],
            [
              -81.81243896484375,
              24.43714786161562
            ],
            [
              -80.58746337890625,
              24.794214972389486
            ],
            [
              -80.08209228515625,
              25.39117928167583
            ]
          ]
        ]});
StormEvents
| where isnotempty(BeginLat) and isnotempty(BeginLon)
| project BeginLon, BeginLat, EventType
| where geo_distance_point_to_polygon(BeginLon, BeginLat, roadToKeyWest) < 500
| render scatterchart with (kind=map)

Screenshot of the result of the previous KQL query to calculate events along a polygon.

Find anomalies based on geospatial data

The following query performs an analysis of storm events occurring within a particular state. The query uses S2 cells and temporal aggregation to investigate patterns of damage. The result is a visual anomaly chart that portrays any irregularities or deviations in storm-induced destruction over time, offering a detailed perspective on the effect of storms within the specified state boundaries.

let stateOfInterest = "Texas";
let statePolygon = materialize(
    US_States
    | extend name = tostring(features.properties.NAME)
    | where name == stateOfInterest
    | project geometry=features.geometry);
let stateCoveringS2cells = statePolygon
    | project s2Cells = geo_polygon_to_s2cells(geometry, 9);
StormEvents
| extend s2Cell = geo_point_to_s2cell(BeginLon, BeginLat, 9)
| where s2Cell in (stateCoveringS2cells)
| where geo_point_in_polygon(BeginLon, BeginLat, toscalar(statePolygon))
| make-series damage = avg(DamageProperty + DamageCrops) default = double(0.0) on StartTime step 7d
| extend anomalies=series_decompose_anomalies(damage)
| render anomalychart with (anomalycolumns=anomalies)

Screenshot of the anomaly chart rendered by the previous KQL query.