Move and Remove Markers on Graphics Layer

2491
6
Jump to solution
11-22-2019 04:23 PM
SteveEllis1
New Contributor II

I'm using the ArcGIS 100.6 iOS SDK and am populating the map with markers on a Graphics Overlay Layer whose locations are stored in a database common to all users of my application. Each marker is stored in a unique record and each record contains the Latitude & Longitude of the marker. When the App is launched it reads the location of all markers in the database and adds each one to the Graphics Overlay as shown below:

   let areaMarker = AGSPictureMarkerSymbol(image: UIImage(named: "CustomMarker")!)

   let areaMarkerLocation = AGSPointMakeWGS84(y ?? 0.0, x ?? 0.0)

   let markerKey = AGSGraphic(geometry: areaMarkerLocation, symbol: areaMarker, attributes: ["marker": markerKey])

   self.overlay.graphics.add(markerKey)

As shown above, each marker is assigned an attribute "marker:markerKey" that is the unique database record number (key) where the marker location information is stored and serves as the marker ID.

Once the initial markers are added to the Overlay, the App "Listens" to the database for the following events:

  • A new marker is added
  • An existing marker is moved to a new location
  • An existing marker is deleted

When a marker is moved or deleted the database listener is notified and passed the record number (key) of the marker that was moved (or deleted). If the marker was moved, the record will then contain the new Latitude & Longitude information.

I have experimented with reading the graphics overlay and determined that it is a Collection contained in a NSMutable Array. I can read all attributes as follows:

   let graphicsCollection = self.overlay.graphics.mutableArrayValue(forKey: "attributes")

   print(graphicsCollection)

The result is:
   (
      {
         marker = "-KlRW2_rba1zBrDPpxSl";
      },
      {
         marker = "-Lu915xF3zQp4dIYnsP_";
      }
   )


I can do the same for "geometry" and get the array of AGSPoints:

   let graphicsCollection = self.overlay.graphics.mutableArrayValue(forKey: "geometry")

   print(graphicsCollection)

The result is:
   (
      "AGSPoint: (-117.826127, 44.781139), sr: 4326",
      "AGSPoint: (-112.056906, 33.629829), sr: 4326"
   )

I have been unable to determine how to get the "index" of the attribute array (e.g. marker "-KlRW2_rba1zBrDPpxSl" above should have an index of [0]) so I can then use that "index" to access the appropriate AGSPoint and update the Latitude & Longitude or remove the marker.

Thanks in advance for the help.

0 Kudos
1 Solution

Accepted Solutions
Nicholas-Furness
Esri Regular Contributor

Hey Steve.

Is there a reason my answer over on StackOverflow won't work for you?

Cheers,

Nick.

P.S. Also, apologies for not seeing this earlier. My RSS feed only just pinged me about it!

View solution in original post

6 Replies
Nicholas-Furness
Esri Regular Contributor

Hey Steve.

Is there a reason my answer over on StackOverflow won't work for you?

Cheers,

Nick.

P.S. Also, apologies for not seeing this earlier. My RSS feed only just pinged me about it!

SteveEllis1
New Contributor II

Nick - Answer on StackOverflow was exactly what I was looking for.  Updated the code and it works fine. Posted there and in GIS forum on StackExchange as well.

Just for reference where is this documented?  I tried to find in docs but must have missed it.

Nicholas-Furness
Esri Regular Contributor

Hey Steve.

The graphics property on AGSGraphicsOverlay is listed as a Mutable Array of AGSGraphics. Unfortunately, the way it's exposed via the SDK, it's not strongly typed that way so you have to tell Swift what its contents are.

For reference, here's the code from SO:

let searchMarker = "-KlRW2_rba1zBrDPpxSl"
let newLocation = AGSPointMakeWGS84(40.7128, -74.0060) // NYC
if let graphic = (overlay.graphics as? [AGSGraphic])?.first(where: {
        ($0.attributes["marker"] as? String) == searchMarker }) {
    // Move the graphic
    graphic.geometry = newLocation
    // Or remove the graphic
    overlay.graphics.remove(graphic)
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In that code the trick is really in the (overlay.graphics as? [AGSGraphic])? which will actually never be nil but rather than fight that (or use explicit unwraps which should really be avoided), since the result of first() could be nil, let's just use optional chaining for a cleaner bit of code.

Once we've (re-)typed the array, the rest is all NSArray functionality, made nicer by Swift closure shortcuts.

Does that answer your question?

SteveEllis1
New Contributor II

Nick:

Yes - this answered my question.  Thanks for the help.  Haven't started to update Android version yet, but assume there is a similar approach to identify the marker by the attribute and update the lat/long?

Steve

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Yeah, there should be. I don't do any Java or Kotlin, but the underlying principles will be the same. You might have to do it old school and loop over all the graphics, checking each one's attributes["marker"] until you find a match. That's what the first(where:) Swift method is doing behind the scenes anyway.

0 Kudos
Nicholas-Furness
Esri Regular Contributor

An update from one of my Java-savvy colleagues (Colin Anderson‌) suggests you could do something like this:

Graphic graphic = graphicsOverlay.getGraphics().stream().filter(g -> g.getAttributes().containsValue("value")).findFirst().orElse(null);

If you know you won't have that "marker" value in any other attribute on the graphic, this'll work nicely. Otherwise you might modify the predicate in the filter() function to be more explicit, but this pattern will work.

0 Kudos