Select to view content in your preferred language

Is there any way to add a label to the middle of a polyline graphic in a sceneView

2165
11
Jump to solution
08-22-2023 01:47 AM
alionthego
New Contributor II

I am creating a poly line graphic to add to a sceneView for a SwiftUI app.  Is there any way to add a text label along this line?

Ideally I would like a box with some text inside it along the direction of the line in the middle of the line if that's achievable.

 

let polyline = Polyline(
    points: [
        point1,
        point2
    ]
)
let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
let polylineGraphic = Graphic(geometry: polyline, symbol: polylineSymbol)

 

1 Solution

Accepted Solutions
Ting
by Esri Contributor
Esri Contributor

Please see the code snippet below. The key here is to set 

overlay.labelsAreEnabled = true

so that the overlay allows displaying labels.

labelsAreEnabled.png

 

import SwiftUI
import ArcGIS

class Model: ObservableObject {
    let lineOverlay: GraphicsOverlay = {
        let textSymbol = TextSymbol(color: .blue, size: 12)
        let expression = SimpleLabelExpression(simpleExpression: "[Name]")
        let labelDefinition = LabelDefinition(labelExpression: expression, textSymbol: textSymbol)
        labelDefinition.placement = .lineCenterAlong
        
        let overlay = GraphicsOverlay()
        overlay.labelsAreEnabled = true
        overlay.addLabelDefinition(labelDefinition)
        
        return overlay
    }()
}

struct ContentView: View {
    @StateObject private var model = Model()
    
    @State private var scene = Scene(basemapStyle: .arcGISLightGray)
    
    @State private var camera: Camera? = Camera(location: Point(x: 113.0, y: 22.0, z: 4_953_394, spatialReference: .wgs84), heading: 360.0, pitch: 0.0, roll: 0)
    
    func addGraphic(latitude: Double, longitude: Double) {
        let polyline = Polyline(
            points: [
                Point(latitude: latitude, longitude: longitude),
                Point(x: 115, y: 24)
            ]
        )
        let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
        let polylineGraphic = Graphic(geometry: polyline, attributes: ["Name": "Hello World"], symbol: polylineSymbol)
        model.lineOverlay.addGraphic(polylineGraphic)
    }
    
    var body: some View {
        SceneView(scene: scene, camera: $camera, graphicsOverlays: [model.lineOverlay])
            .onAppear {
                addGraphic(latitude: 39, longitude: 120)
            }
    }
}

 

 

View solution in original post

0 Kudos
11 Replies
MichaelBranscomb
Esri Frequent Contributor

Here's a sample that shows how to setup labels for a layer (albeit a polygon layer in this case, but the pattern is basically the same): Show labels on layer.

And here's the full technical Labeling properties manual that provides detailed information on how the properties that you see in ArcGIS Pro map onto API types exposed in Swift.

Even if you don't currently have access to the ArcGIS Pro desktop software, I recommend looking at the documentation to determine the properties that will help you achieve the effect you're looking for in the API. 

For placing a label "along the direction of the line in the middle of the line" you probably want to use 1.14.0 placement property value `lineCenterAlong` which means Center of label prefers the midpoint of the geometry, label follows the geometry segments. And the symbol is determined by the TextSymbol | Documentation you specify.

0 Kudos
alionthego
New Contributor II

Hi.  Thanks for the assistance.  I've tried to add this using just some example text because I haven't figured out the $feature. yet but the example text is not displayed on the poly lines in my layer.   Using this method am I able to add labels to a GraphicsLayer consisting of Polyline Graphics?

In the code below myOverlay is a GraphicsOverlay which I have added graphics to that have polyline geometry and SimpleLineSymbol.    The graphicsOverlay appears but without the Text Test above each line.

let textSymbol = TextSymbol(color: .blue, size: 12)
let expression = "Test"
let arcadeLabelExpression = ArcadeLabelExpression(arcadeString: expression)
let labelDefinition = LabelDefinition(labelExpression: arcadeLabelExpression, textSymbol: textSymbol)
labelDefinition.placement = .lineCenterAlong
myOverlay.addLabelDefinition(labelDefinition)

   

0 Kudos
MichaelBranscomb
Esri Frequent Contributor

If you want to display arbitrary text, then a simple label expression may be better initially: SimpleLabelExpression | Documentation (arcgis.com)

For ArcadeLabelExpressions I recommend browsing the doc Introduction | ArcGIS Arcade | ArcGIS Developers and then also trying out the Playground | ArcGIS Arcade | ArcGIS Developers. Essentially Arcade enables you to create label expressions that are derived from attributes in the data and can include more advanced behaviors such as calculations and formatting.

0 Kudos
alionthego
New Contributor II

Thanks.  I tried the SimpleLabelExpression as well but nothing is appearing.  Not sure if the problem is that these only work with FeatureLayers and not GraphicOverlays or maybe my Lite Licence does not allow this.

0 Kudos
MichaelBranscomb
Esri Frequent Contributor

Labels should work for both Features and Graphics and at the Lite license level. 

Can you share your full code snippet?

0 Kudos
alionthego
New Contributor II

This is a sample project.  I guess I must be doing something fundamentally wrong because when I place this view in a NavigationSplitView crashes right away.  But without the NavigationSplitView it works fine.  In this example the line is drawn but there is not text on the line.

 

import SwiftUI
import ArcGIS

class ViewModel: ObservableObject {
    
    @Published var lineOverlay = GraphicsOverlay()
    
}

struct GeographyView: View {
    
    @StateObject var viewModel = ViewModel()
        
    @State private var scene = Scene()
    @State private var mobileScenePackage: MobileScenePackage!
    @State var camera: Camera? = Camera(location: Point(x: 113.0, y: 22.0, z: 4_953_394, spatialReference: .wgs84), heading: 360.0, pitch: 0.0, roll: 0)
    
    private func loadMobileScenePackage() async throws {
        let sceneURL = Bundle.main.url(forResource: "satteliteImageryScene", withExtension: "mspk")!
        self.mobileScenePackage = MobileScenePackage(fileURL: sceneURL)
        try await mobileScenePackage.load()
        guard let scene = mobileScenePackage.scenes.first else { return }
        let x = 113.0
        let y = 22.0
        scene.initialViewpoint = Viewpoint(latitude: y, longitude: x, scale: 4_953_394)
        addOverlay(x: x, y: y)
        self.scene = scene
    }
    
    func addOverlay(x: Double, y: Double) {
        let overlay = GraphicsOverlay()
        let point1 = Point(x: x, y: y, spatialReference: .wgs84)
        let point2 = Point(x: 115, y: 24)
        let polyline = Polyline(
            points: [
                point1,
                point2
            ]
        )
        let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
        let polylineGraphic = Graphic(geometry: polyline, symbol: polylineSymbol)
        viewModel.lineOverlay.addGraphic(polylineGraphic)
        
        let textSymbol = TextSymbol(color: .blue, size: 12)
        let expression = "Test"
        let simpleExpression = SimpleLabelExpression(simpleExpression: "Hello")
        let labelDefinition = LabelDefinition(labelExpression: simpleExpression, textSymbol: textSymbol)
        labelDefinition.placement = .lineCenterAlong
        viewModel.lineOverlay.addLabelDefinition(labelDefinition)
    }
    
    // MARK: - Body

    var body: some View {
        NavigationStack {
            SceneViewReader { sceneView in
                ZStack {
                    SceneView(scene: scene, camera: $camera, graphicsOverlays: [viewModel.lineOverlay])
                        .task {
                            do {
                                try await loadMobileScenePackage()
                            } catch {
                                print(error)
                            }
                        }
                }
            }
        }
    }
}
0 Kudos
Ting
by Esri Contributor
Esri Contributor

Please see the code snippet below. The key here is to set 

overlay.labelsAreEnabled = true

so that the overlay allows displaying labels.

labelsAreEnabled.png

 

import SwiftUI
import ArcGIS

class Model: ObservableObject {
    let lineOverlay: GraphicsOverlay = {
        let textSymbol = TextSymbol(color: .blue, size: 12)
        let expression = SimpleLabelExpression(simpleExpression: "[Name]")
        let labelDefinition = LabelDefinition(labelExpression: expression, textSymbol: textSymbol)
        labelDefinition.placement = .lineCenterAlong
        
        let overlay = GraphicsOverlay()
        overlay.labelsAreEnabled = true
        overlay.addLabelDefinition(labelDefinition)
        
        return overlay
    }()
}

struct ContentView: View {
    @StateObject private var model = Model()
    
    @State private var scene = Scene(basemapStyle: .arcGISLightGray)
    
    @State private var camera: Camera? = Camera(location: Point(x: 113.0, y: 22.0, z: 4_953_394, spatialReference: .wgs84), heading: 360.0, pitch: 0.0, roll: 0)
    
    func addGraphic(latitude: Double, longitude: Double) {
        let polyline = Polyline(
            points: [
                Point(latitude: latitude, longitude: longitude),
                Point(x: 115, y: 24)
            ]
        )
        let polylineSymbol = SimpleLineSymbol(style: .solid, color: .cyan, width: 2.0)
        let polylineGraphic = Graphic(geometry: polyline, attributes: ["Name": "Hello World"], symbol: polylineSymbol)
        model.lineOverlay.addGraphic(polylineGraphic)
    }
    
    var body: some View {
        SceneView(scene: scene, camera: $camera, graphicsOverlays: [model.lineOverlay])
            .onAppear {
                addGraphic(latitude: 39, longitude: 120)
            }
    }
}

 

 

0 Kudos
Ting
by Esri Contributor
Esri Contributor

To add to Michael's comment, if you only want to create a temporary symbol for a graphic, you can also use TextSymbol API. For example, the code below adds a text to the polyline at the middle with some offset.

 

let lineSymbol = SimpleLineSymbol(style: .solid, color: .red, width: 3)
let textSymbol = TextSymbol(text: "1", color: .yellow, size: 20, horizontalAlignment: .center, verticalAlignment: .middle)
textSymbol.offsetX = 10
let compositeSymbol = CompositeSymbol(symbols: [lineSymbol, textSymbol])

let polyline = Polyline(points: [Point(latitude: 0, longitude: 0), Point(latitude: 10, longitude: 0)])
let graphic = Graphic(geometry: polyline, symbol: compositeSymbol)

 

However, from a cartography standpoint, it would be better to author the feature layers with labels, or even renderers (that define what symbol should be applied to what features with certain attributes) baked-in, and directly consume it on a mobile device.

0 Kudos
alionthego
New Contributor II

I have tried composite symbol before but it is not nicely along the line.  I believe Michael's approach is more along the lines of what I wanted.  Mind the Pun.