Draw object clustering(GDK5)
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.
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.
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"
}
]
}
]
}