Tutorial: Create geospatial visualizations

This tutorial is for those who want to use Kusto Query Language (KQL) for geospatial visualization. Geospatial clustering is a way to organize and analyze data based on geographical location. KQL offers multiple methods for performing geospatial clustering and tools for geospatial visualizations.

In this tutorial, you'll learn how to:

Plot points on a map

To visualize points on a map, use project to select the column containing the longitude and then the column containing the latitude. Then, use render to see your results in a 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 series of points, use project to select the longitude and latitude along with a third column, which defines the series.

In the following query, the series is EventType. The points are colored differently according to their EventType, and when selected display the content of the EventType column.

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

Screenshot of sample storm events on a map by type.

You may also explicitly specify the xcolumn (Longitude), ycolumn (Latitude), and series when performing the render. This specification is necessary when there are more columns in the result than just the longitude, latitude, and series columns.

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

Use GeoJSON values to plot points on a map

A dynamic GeoJSON value can change or be updated and are often used for real-time mapping applications. Mapping points using dynamic GeoJSON values allows for more flexibility and control over the representation of the data on the map that may not be possible with plain latitude and longitude values.

The following query uses the geo_point_to_s2cell and geo_s2cell_to_central_point to map storm events in 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 the distribution of data points by performing an aggregation in each cluster and then plotting the central point of the cluster.

For example, the following query filters for all storm events of the "Tornado" event type. It then groups the events into clusters based on their longitude and latitude, counts the number of events in each cluster, and projects the central point of the cluster, and renders a map to visualize the result. The regions with the most tornados become clearly detected based on their large 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 web UI showing a geospatial map of tornado storms.

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.