Draw object clustering(GDK5)

From Maria GDK Wiki
Jump to navigation Jump to search

Overview

Draw object clustering is used to

  • Make complex situations easier to comprehend
  • Increase performance by reducing information in map

Clustering allows a very large amount of objects to be displayed, without decoding the object data.

Currently, two clustering modes are available:

  • Grid clustering, groups dense clusters of point and area objects and display these as circular or bounded areas
  • Time fade clustering/filter will only display objects that are available within a time window. Fade times can be set globally or per object.


Grid clustering

Usage from MariaApplication

Clustering is controlled from "Draw" tab, "Clustering". Select algorithm and set parameters. Note that depending on selected algorithm, only some parameters may be actively used.

Clustering control in MariaApplication

Usage from API

The Maria API "DrawObjectLayer" contains a property "ClusterSettings". Set properties on this object to control clustering

/// <summary>
/// Cluster algorithm definitions
/// </summary>
public enum ClusteringAlgorithm
{
    /// <summary>
    /// No clustering.
    /// </summary>
    None,
    /// <summary>
    /// Grid clustering (not used)
    /// </summary>
    GridClustering,

    /// <summary>
    /// TimeFilterClustering is used to only show objects within a time range.
    /// Note that this can also be done using styling. Cluster version is much faster
    /// </summary>
    TimeFilterClustering = 3
}

/// <summary>
/// Interface for draw object clustering. 
/// </summary>
public interface IDrawObjectClustering
{
    /// <summary>
    /// Get or set clustering type.
    /// </summary>
    ClusteringAlgorithm Algorithm { get; set; }     // Algorithm controlling clustering type

    // Grid clustering specific
    int MinItemCount { get; set; }                  // Minimum item cont per cluster in order to perform clustering
    int DetailedObjectInfoThreshold { get; set; }   // If a cluster items <= x, detailed info is included in clustered, including positions and sidc
    int MaxAreaPixelExtent { get; set; }            // Only areas with extent <= this value are considered for clustering
    bool GenerateItemBoundary { get; set; }         // If clustering method supports this, generate cluster boundaries
    bool IncludePoints { get; set; }                // If clustering method supports this, include points in clusters

    int MaxZoomLevelPoints { get; set; }            // For larger scales than this, do not cluster points. Primarily for use in supercluster (currently not used)
    int MaxZoomLevelAreas { get; set; }             // For larger scales than this, do not cluster points. Primarily for use in supercluster (currently not used)
    int CellSize { get; set; }                      // Cell size in pixels

    // TimeFilterClustering specific
    string IsoStartTimeString { get; set; }         // String representation (ISO8601) of filter start time
    DateTimeOffset StartTime { get; set; }          // Filter start time
    uint DefaultFadeTime { get; set; }              // Default fade time in seconds. Used if not set directly on object
    int OffsetSeconds { get; set; }                 // Offset in seconds from StartTime. Actual filtering is StartTime + OffsetSeconds to StartTime + OffsetSeconds + Fade time
}

Bitfield coding for time clustering

In order to use time clustering, the geo store must be created so that time values are encoded into the geo index. Note that this must be set when the store is created.

var storeInfo = new StoreInfo() { Id = "SomeStore", IsInProcessStore = true|false,
    BitFieldCodingSpec = BitFieldCoder.GenerateTimeFadeCodingSpec(
        "APP6D",
        "timeStamp",
        "timeFade")
};
DrawObjectLayer = _drawObjectSystemFactory.CreateLayer(GeoControlViewModel, storeInfo);

The encoding specification required for time fading (provided by GenerateTimeFadeCodingSpec):

public static BitFieldDefinitions GenerateTimeFadeCodingSpec(string app6DField, string timeStampField, string fadeTimeField)
{
    return new BitFieldDefinitions
    {
        Fields =
        [
            new BitFieldDefinition
            {
                Id="App6d_Sidc",
                FieldRef=app6DField,
                DataType = BitFieldDataType.App6DSidcType
            },
            new BitFieldDefinition
            {
                Id="TimeStamp",
                FieldRef=timeStampField,
                DataType = BitFieldDataType.UtcTimeStamp1970,
                FirstBit = 0,
                LastBit = 39,
                IntScaleFactor = 10
            },
            new BitFieldDefinition
            {
                Id="FadeTime",
                FieldRef=fadeTimeField,
                DataType = BitFieldDataType.IntType,
                FirstBit = 40,
                LastBit = 49,
                IntScaleFactor = 10
            }
        ]
    };
}

Bitfield coding

Bitfield coding is a mechanism used to embed draw object data directly into the geo index. This allows very fast clustering and filtering for critical data fields. The embedded data are limited to:

  • Two numeric fields for APP6D SIDC (Digits 1-10 and 11-20). For now, digits [21-30] are ignored until there is a specific use case for clustering and filtering based on these digits
  • One 64 bit integer used to encode general information

Database representation

The encoding specification is fixed for each GeoStore database and should only be set on creation. We may add regeneration of fields if required in the future. It is possible to encode value tables (strings), scaled integers, time values and APP6D codes. Note that App6DSidcType is a special case. Values wll be encoded directly into the relevant SIDC fields in database table "object_info", columns "sidc_set_a" and "sidc_set_b"


A JSON specification is placed in the "coded_values_definition" in "dataset_resources" in the database. It can be constructed programatically by JSON serialize of the GDK class BitFieldDefinitions:

public enum BitFieldDataType
{
    StringType, IntType, IntRangeType, App6DSidcType, UtcTimeStamp1970
}

public class ConditionalBitField
{
    public string FieldRef { get; set; } = null!;
    public string[] Values { get; set; } = null!;
}

public class BitFieldValue
{
    public uint Code { get; set; }
    public string? Value { get; set; }
    public int? IntValue { get; set; }
    public int? MinValue { get; set; }
    public int? MaxValue { get; set; }
}

public class BitFieldDefinition
{
    public string Id { get; set; } = null!;
    public string FieldRef { get; set; } = null!;

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public BitFieldDataType DataType { get; set; }
    public ConditionalBitField? Conditional { get; set; }
    public int FirstBit { get; set; }
    public int LastBit { get; set; }
    public BitFieldValue[] Values { get; set; } = {};
    public int ?IntScaleFactor { get; set; }
    public int ?IntOffset { get; set; }
}

Sample json:

{
  "Fields": [
    {
      "Id": "App6d_Sidc",
      "FieldRef": "APP6D",
      "DataType": "app6DSidcType"
    },
    {
      "Id": "Object_type",
      "FieldRef": "objtype",
      "DataType": "stringType",
      "FirstBit": 0,
      "LastBit": 3,
      "Values": [
        {
          "Code": 1,
          "Value": "Kumlokk"
        },
        {
          "Code": 2,
          "Value": "Lysarmatur"
        },
        {
          "Code": 3,
          "Value": "Mast"
        },
        {
          "Code": 4,
          "Value": "Nettverkstasjon"
        },
        {
          "Code": 5,
          "Value": "Skap"
        },
        {
          "Code": 6,
          "Value": "Flymarkør"
        },
        {
          "Code": 7,
          "Value": "Vindturbin"
        },
        {
          "Code": 8,
          "Value": "AnnenBygning"
        },
        {
          "Code": 9,
          "Value": "Bygning"
        },
        {
          "Code": 10,
          "Value": "Takoverbygg"
        },
        {
          "Code": 11,
          "Value": "AisTrack"
        }
      ]
    }
  ]
}