Create a custom dynamic entity data source and display it using a dynamic entity layer.
Use case
Developers can create a custom DynamicEntityDataSource
to be able to visualize data from a variety of different feeds as dynamic entities using a DynamicEntityLayer
. An example of this is in a mobile situational awareness app, where a custom DynamicEntityDataSource
can be used to connect to peer-to-peer feeds in order to visualize real-time location tracks from teammates in the field.
How to use the sample
Run the sample to view the map and the dynamic entity layer displaying the latest observation from the custom data source. Tap on a dynamic entity to view its attributes in a callout.
How it works
- Create a
DynamicEntityLayer
in theMap
component. - Update values in the layer's
TrackDisplayProperties
to customize the layer's appearance. - Set up the layer's
LabelDefinitions
to display labels for each dynamic entity. - Use a custom data source implementation of a
DynamicEntityDataSource
for the layer'sdataSource
property. - Override
OnLoadAsync()
to specify theDynamicEntityDataSourceInfo
for a given unique entity ID field and a list ofField
objects matching the fields in the data source. - Override
OnConnectAsync()
to begin processing observations from the custom data source by starting a timer. - Use a
Timer
to iterate through the observations file at a given interval and deserialize each observation into aPoint
object and aQVariantMap
containing the attributes. - Use
DynamicEntityDataSource.addObservation(point, attributes)
to add each observation to the custom data source. - Override
OnDisconnectAsync()
to define how the data source will stop processing observations. - Use
MapView.identifyLayer
to select a dynamic entity and display the entity's attributes in aCallout
.
Relevant API
- DynamicEntity
- DynamicEntityDataSource
- DynamicEntityLayer
- LabelDefinition
- TrackDisplayProperties
About the data
This sample uses a .json file containing observations of marine vessels in the Pacific North West hosted on ArcGIS Online .
Additional information
In this sample, we iterate through features in a GeoJSON file to mimic messages coming from a real-time feed. You can create a custom dyamic entity data source to process any data that contains observations which can be translated into MapPoint
objects with associated QVariantMap attributes.
Tags
data, dynamic, entity, label, labeling, live, real-time, stream, track
Sample Code
// [WriteFile Name=AddCustomDynamicEntityDataSource, Category=Layers]
// [Legal]
// Copyright 2023 Esri.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// [Legal]
import QtQuick
import Esri.ArcGISExtras
import Esri.ArcGISRuntime
import Esri.ArcGISRuntime.Toolkit
Rectangle {
id: rootRectangle
clip: true
width: 800
height: 600
property DynamicEntity dynamicEntity: null
MapView {
id: mapView
anchors.fill: parent
Component.onCompleted: {
// Set and keep the focus on MapView to enable keyboard navigation
forceActiveFocus();
}
Map {
initBasemapStyle: Enums.BasemapStyleArcGISOceans
initialViewpoint: ViewpointCenter {
center: Point {
x: -123.657
y: 47.984
spatialReference: SpatialReference { wkid: 4326 }
}
targetScale: 3e6
}
DynamicEntityLayer {
id: dynamicEntityLayer
trackDisplayProperties {
showPreviousObservations: true
showTrackLine: true
maximumObservations: 20
}
LabelDefinition {
expression: SimpleLabelExpression {
expression: "[VesselName]"
}
textSymbol: TextSymbol {
color: "red"
size: 12
}
placement: Enums.LabelingPlacementPointAboveCenter
}
labelsEnabled: true
// Create a CustomDynamicEntityDataSource which inherits from DynamicEntityDataSource
dataSource: CustomDynamicEntityDataSource {
id: customDynamicEntityDataSource
purgeOptions.maximumObservationsPerTrack: 20
// onLoadAsync must be defined
// This loads the data source and returns a DynamicEntityDataSourceInfo
onLoadAsync: {
const fieldList = createFieldList();
const dynamicEntityDataSourceInfo = ArcGISRuntimeEnvironment
.createObject("DynamicEntityDataSourceInfo",
{
entityIdFieldName: "MMSI",
fields: fieldList,
spatialReference: Factory.SpatialReference.createWgs84()
});
// loadAsyncCompleted() must be called
loadAsyncCompleted(dynamicEntityDataSourceInfo);
}
// onConnectAsync must be defined
onConnectAsync: {
// Start the timer to begin the asynchronous task
timer.start();
// This method must be called
connectAsyncCompleted();
}
// onDisconnectAsync must be defined
onDisconnectAsync: {
// This method must be called
disconnectAsyncCompleted();
}
}
}
}
onMouseClicked: (mouseClick) => {
// Clear any previous information if necessary
mapView.calloutData.visible = false;
dynamicEntity = null;
// Identify a potential feature at the mouse click location
mapView.identifyLayer(dynamicEntityLayer, mouseClick.x, mouseClick.y, 10, false);
}
onIdentifyLayerStatusChanged: {
if (identifyLayerStatus !== Enums.TaskStatusCompleted)
return;
if (identifyLayerResult.geoElements.length === 0)
return;
dynamicEntity = identifyLayerResult.geoElements[0].dynamicEntity();
// Update the Callout every time the dynamic entity observation updates
dynamicEntity.onDynamicEntityChanged.connect(updateCallout);
}
}
// Update the callout with a new location and attribute information
function updateCallout() {
if (!dynamicEntity)
return;
const observation = dynamicEntity.latestObservation();
let attributes = observation.attributes
mapView.calloutData.geoElement = observation;
const calloutText = "Vessel Name: " + attributes.attributeValue("VesselName") + "\n" +
"Call Sign: " + attributes.attributeValue("CallSign") + "\n" +
"Course over Ground: " + attributes.attributeValue("COG") + "º\n" +
"Speed over Ground: " + attributes.attributeValue("SOG") + " knots"
mapView.calloutData.detail = calloutText;
mapView.calloutData.location = observation.geometry;
mapView.calloutData.visible = true;
}
Callout {
id: callout
height: 120
calloutData: mapView.calloutData
accessoryButtonVisible: false
}
// The .json file holding all observation information, specific to this sample
FileFolder {
id: jsonFile
url: "qrc:/Samples/Layers/AddCustomDynamicEntityDataSource/"
property var observationList: []
Component.onCompleted: {
observationList = readTextFile("AIS_MarineCadastre_SelectedVessels_CustomDataSource.json").split("\n");
}
}
// A timer to read each line of the file at a given interval of 10ms and call addObservation with the information
Timer {
id: timer
interval: 10
repeat: true
property int i: 0
onTriggered: {
const line = JSON.parse(jsonFile.observationList[i]);
const geometry = line["geometry"];
const attributes = line["attributes"];
const point = ArcGISRuntimeEnvironment.createObject("Point", {x: geometry["x"], y: geometry["y"], spatialReference: Factory.SpatialReference.createWgs84()});
customDynamicEntityDataSource.addObservation(point, attributes);
i++;
if (i >= jsonFile.observationList.length)
i = 0;
}
}
// This field list is specifically written for the sample's fields
function createFieldList() {
return [ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "MMSI", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "BaseDateTime", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "LAT", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "LONG", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "SOG", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "COG", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "Heading", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "VesselName", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "IMO", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "CallSign", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "VesselType", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "Status", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "Length", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeFloat64, name: "Width", length: 8}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "Cargo", length: 256}),
ArcGISRuntimeEnvironment.createObject("Field", {fieldType: Enums.FieldTypeText, name: "globalid", length: 256})];
}
}