Skip to content
View on GitHub

Create a custom dynamic entity data source and display it using a dynamic entity layer.

Image of the add custom dynamic entity data source sample

Use case

Developers can create a CustomDynamicEntityDataSource 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 CustomDynamicEntityDataSource 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

  1. Create the metadata for the data source using DynamicEntityDataSourceInfo for a given unique entity ID field and an array of Field objects matching the fields in the data source.
  2. Create your custom feed type that conforms to CustomDynamicEntityFeed which will implement the data feed that will asynchronously emit CustomDynamicEntityFeedEvent.
  3. The feed should loop through the observations JSON and deserialize each observation into a Point object and a Dictionary<String, Any> containing the attributes.
  4. Use CustomDynamicEntityFeedEvent.newObservation(geometry:attributes:) to add each event to the feed.
  5. Create a custom data source using CustomDynamicEntityDataSource.

Relevant API

  • CustomDynamicEntityDataSource
  • CustomDynamicEntityFeed
  • CustomDynamicEntityFeedEvent
  • DynamicEntity
  • DynamicEntityDataSource
  • DynamicEntityLayer
  • LabelDefinition
  • TrackDisplayProperties

About the data

This sample uses a JSON Lines file containing observations of marine vessels in the Pacific North West hosted on ArcGIS Online.

Tags

data, dynamic, entity, label, labeling, live, real-time, stream, track

Sample Code

AddCustomDynamicEntityDataSourceView.swiftAddCustomDynamicEntityDataSourceView.swiftAddCustomDynamicEntityDataSourceView.Vessel.swift
Use dark colors for code blocksCopy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 // 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 // // https://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.  import ArcGIS import SwiftUI  struct AddCustomDynamicEntityDataSourceView: View {  /// A map with an ArcGIS oceans basemap style and a dynamic entity layer.  @State private var map = makeMap()   /// The dynamic entity layer that is displaying our custom data.  private var dynamicEntityLayer: DynamicEntityLayer {  map.operationalLayers.first as! DynamicEntityLayer  }   /// The point on the screen the user tapped.  @State private var tappedScreenPoint: CGPoint?   /// The placement of the callout.  @State private var calloutPlacement: CalloutPlacement?   var body: some View {  MapViewReader { proxy in  MapView(map: map)  .onSingleTapGesture { screenPoint, _ in  tappedScreenPoint = screenPoint  }  .callout(placement: $calloutPlacement.animation(.default.speed(2))) { placement in  let attributes = (placement.geoElement as! DynamicEntityObservation).attributes  VStack(alignment: .leading) {  // Display all the attributes in the callout.  ForEach(attributes.sorted(by: { $0.key < $1.key }), id: \.key) { item in  Text("\(item.key): \(String(describing: item.value))")  }  }  }  .task(id: tappedScreenPoint) {  let newCalloutPlacement: CalloutPlacement?  if let tappedScreenPoint,  let identifyResult = try? await proxy.identify(  on: dynamicEntityLayer,  screenPoint: tappedScreenPoint,  tolerance: 2  ) {  // Set the callout placement to the observation that was tapped on.  newCalloutPlacement = identifyResult.geoElements.first.map { .geoElement($0) }  } else {  // Hides the callout.  newCalloutPlacement = nil  }  calloutPlacement = newCalloutPlacement  }  }  }   /// Makes a map with a dynamic entity layer.  private static func makeMap() -> Map {  let map = Map(basemapStyle: .arcGISOceans)  map.initialViewpoint = Viewpoint(  latitude: 47.984,  longitude: -123.657,  scale: 3e6  )   // The meta data for the custom dynamic entity data source.  let info = DynamicEntityDataSourceInfo(  entityIDFieldName: "MMSI",  fields: [  Field(type: .text, name: "MMSI", alias: "MMSI", length: 256),  Field(type: .float64, name: "SOG", alias: "SOG", length: 8),  Field(type: .float64, name: "COG", alias: "COG", length: 8),  Field(type: .text, name: "VesselName", alias: "VesselName", length: 256),  Field(type: .text, name: "CallSign", alias: "CallSign", length: 256)  ]  )   info.spatialReference = .wgs84   // Create our custom data source from our custom data feed.  let customDataSource = CustomDynamicEntityDataSource(info: info) { VesselFeed() }   let dynamicEntityLayer = DynamicEntityLayer(dataSource: customDataSource)   // Set display tracking properties on the layer.  let trackDisplayProperties = dynamicEntityLayer.trackDisplayProperties  trackDisplayProperties.showsPreviousObservations = true  trackDisplayProperties.showsTrackLine = true  trackDisplayProperties.maximumObservations = 20   // Create the label definition so we can show the vessel name on top of  // each dynamic entity.  let labelDefinition = LabelDefinition(  labelExpression: SimpleLabelExpression(simpleExpression: "[VesselName]"),  textSymbol: TextSymbol(color: .red, size: 12)  )  labelDefinition.placement = .pointAboveCenter   dynamicEntityLayer.addLabelDefinition(labelDefinition)  dynamicEntityLayer.labelsAreEnabled = true   map.addOperationalLayer(dynamicEntityLayer)   return map  } }  /// The vessel feed that is emitting custom dynamic entity events. private struct VesselFeed: CustomDynamicEntityFeed {  let events = URL.selectedVesselsDataSource.lines.map { line in  // Delay observations to simulate live data.  try await Task.sleep(nanoseconds: 10_000_000)   let decoder = JSONDecoder()  let vessel = try decoder.decode(  AddCustomDynamicEntityDataSourceView.Vessel.self,  from: line.data(using: .utf8)!  )   // The location of the vessel that was decoded from the JSON.  let location = vessel.geometry   // We successfully decoded the vessel JSON so we should  // add that vessel as a new observation.  return CustomDynamicEntityFeedEvent.newObservation(  geometry: Point(x: location.x, y: location.y, spatialReference: .wgs84),  attributes: vessel.attributes  )  } }  private extension URL {  /// The URL to the selected vessels JSON Lines data.  static var selectedVesselsDataSource: URL {  Bundle.main.url(  forResource: "AIS_MarineCadastre_SelectedVessels_CustomDataSource",  withExtension: "jsonl"  )!  } }

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.