|
POST
|
Although we don't have an OD Cost Matrix specific API, the OD Cost Matrix service looks like a regular GP Service. You should be able to use the AGSGeoprocessingTask and create a AGSGeoprocessingJob. Here's an example: Viewshed (Geoprocessing) | ArcGIS for Developers Runtime will just make use of the cached credentials to run the GP job. Also, a quick plug for the Job Manager toolkit component, in case you weren't familiar with it. Let me know if that helps.
... View more
12-16-2019
03:03 PM
|
0
|
0
|
1002
|
|
POST
|
Hmm. This is odd. I am unable to reproduce this in my testing using openweatherdata.org tiles… Which version of Runtime are you using? In case anyone's interested, here's the code I used to try to test: import UIKit
import ArcGIS
let apiKey = "GET ONE FROM OPENWEATHERDATA.ORG - FREE ACCOUNT REQUIRED"
let layerNames = ["clouds_new", "temp_new", "precipitation_new"]
class ViewController: UIViewController {
@IBOutlet weak var mapView: AGSMapView!
var tiledLayers: [AGSWebTiledLayer]? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let map = AGSMap(basemap: AGSBasemap.streetsVector())
mapView.map = map
let layers = layerNames.map({ layerName -> AGSWebTiledLayer in
let urlTemplate = "https://tile.openweathermap.org/map/\(layerName)/{level}/{col}/{row}.png?appid=\(apiKey)"
let layer = AGSWebTiledLayer(urlTemplate: urlTemplate)
layer.opacity = 1.0
layer.isVisible = false
// if let rc = AGSRequestConfiguration.global().copy() as? AGSRequestConfiguration {
// rc.debugLogResponses = true
// layer.requestConfiguration = rc
// }
return layer
})
map.operationalLayers.addObjects(from: layers)
tiledLayers = layers
tiledLayers?.first?.isVisible = true
mapView.touchDelegate = self
}
}
extension ViewController: AGSGeoViewTouchDelegate {
func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
guard let layers = mapView.map?.operationalLayers as? [AGSLayer] else { fatalError("Layers are not layers!") }
guard let firstLayer = layers.first else { return }
let newVisible = !firstLayer.isVisible
layers.forEach {
$0.isVisible = newVisible
}
}
}
... View more
12-10-2019
03:17 PM
|
0
|
2
|
1857
|
|
POST
|
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.
... View more
12-05-2019
10:37 AM
|
0
|
0
|
4803
|
|
POST
|
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.
... View more
12-05-2019
09:57 AM
|
0
|
1
|
4803
|
|
POST
|
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?
... View more
12-04-2019
11:11 AM
|
2
|
0
|
4803
|
|
POST
|
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 more
12-04-2019
10:47 AM
|
2
|
2
|
4803
|
|
POST
|
Hi Cory, Try adding a filetype suffix to the filename. Right now you're naming them attachment0, attachment1, attachment2 etc. Can you try naming them attachment0.txt, attachment1.txt, etc.? And as Michael suggested, I would use EXIF data and store them as images. That way they'll be easily accessible to other components of the ArcGIS platform. Nick
... View more
12-03-2019
05:25 PM
|
1
|
1
|
3270
|
|
POST
|
300,000 markers seems like quite a lot and at a certain point you might find that device memory simply isn't enough. Some possible optimizations: If you have a lot of attribution on each AGSGraphic, you could reduce that. Consider using a Renderer and setting that on AGSGraphicsOverlay. If all your markers are a single symbol, use the AGSSimpleRenderer and create one symbol that runtime re-uses. If your markers have different symbols derived from attribution, you could use an AGSUniqueValueRenderer or AGSClassBreaksRenderer. When you have that much data, it is usually unnecessary to have it all loaded at once (it's either partially off the visible map area, or else if it's all being displayed it's unreadable or better represented in some processed way): Do you need to have 300,000 markers at a time? That's a very "busy" map and you won't be able to see them all at once. Perhaps consider clustering the data, or aggregating it into something more suitable for display (e.g. polygons). You could look at clusterlayer-plugin-ios (in particular the runtime-100 branch, which is still under development but pretty close to finished). Have you considered keeping this data in a Feature Service (or a local Geodatabase)? Runtime would then only retrieve and bring into memory the markers that it needs for display.
... View more
12-02-2019
08:26 AM
|
2
|
0
|
1621
|
|
POST
|
Thanks for this info. I assume this is a duplicate of this question: https://community.esri.com/thread/244598-map-didnt-load-when-choose-trusthostandcontinue-on-ios Can you share the code you used to implement the AGSAuthenticationManager delegate?
... View more
12-02-2019
08:15 AM
|
0
|
0
|
1556
|
|
POST
|
Do you get the error if ArcGIS.framework is not added but you switch to iPad iOS 9?
... View more
11-27-2019
08:44 AM
|
0
|
0
|
626
|
|
POST
|
Anything that makes a network request will use requestOperation. Can you provide more of the stack trace?
... View more
11-27-2019
08:40 AM
|
0
|
1
|
1343
|
|
POST
|
Yep. I'll look into that. Also, note that there was some overkill. We don't need to provide a credential at all since AGSJSONRequestOperation (and AGSRequestOperation) will automatically use the credential that was previously used to display the AGSArcGISMapImageServiceLayer in the first place. D'oh! I've updated my last response to reflect this. Here's a more refined Gist which does propagate errors a little better: Custom Query · GitHub
... View more
11-22-2019
09:01 AM
|
0
|
0
|
2509
|
|
POST
|
This should get you to where you want to get. It's a bit limited. It returns AGSGraphics rather than an AGSFeatureSet of some sort, but I think it'll do what you need: func doQuery(mapPoint: AGSPoint, fields: [String] = ["*"], returnGeometry: Bool = true, completion: (([AGSGraphic]?) -> Void)? = nil) {
guard let serviceURL = layer.url?.appendingPathComponent("\(3)") else { return }
var mapPointData: Data?
do {
let mapPointJSON = try mapPoint.toJSON()
mapPointData = try JSONSerialization.data(withJSONObject: mapPointJSON)
} catch {
print("Could not convert AGSPoint to JSON: \(error.localizedDescription)")
completion?(nil)
return
}
guard mapPointData != nil,
let mapPointJSONString = String(data: mapPointData!, encoding: .utf8) else {
completion?(nil)
return
}
// Assuming that Web Mercator is a good fallback.
let inSR = mapPoint.spatialReference ?? AGSSpatialReference.webMercator()
let outSR = mapView.spatialReference ?? AGSSpatialReference.webMercator()
// Stolen from what a Query sends
let parameters: [String : Any] = [
"f": "json",
"geometry": mapPointJSONString,
"geometryType": "esriGeometryPoint",
"inSR": inSR.wkid,
"maxAllowableOffset": 0.000000,
"outFields": fields.joined(separator: ","),
"outSR":outSR.wkid,
"returnDistinctValues": false,
"returnGeometry": returnGeometry,
"returnM": true,
"returnZ": true,
"spatialRel": "esriSpatialRelIntersects"
]
let queryUrl = serviceURL.appendingPathComponent("query")
let operation = AGSJSONRequestOperation(remoteResource: nil, url: queryUrl, queryParameters: parameters)
operation.registerListener(self) { (result, error) in
if let error = error {
print("Error performing query! \(error.localizedDescription)")
completion?(nil)
return
}
guard let result = result as? [String: Any],
let fields = (result["fields"] as? [Any])?.compactMap({ try? AGSField.fromJSON($0) as? AGSField }),
let graphics = (result["features"] as? [[String: Any]])?.compactMap({ data -> AGSGraphic? in
let attributes = data["attributes"] as? [String: Any]
let geometry: AGSGeometry? = {
if let geomDict = data["geometry"] as? [String: Any] {
let geom = try? AGSGeometry.fromJSON(geomDict) as? AGSGeometry
if let geom = geom, geom.spatialReference == nil {
// Bit of a hack to force an SR onto the geometry
return AGSGeometryEngine.projectGeometry(geom, to: outSR)
}
return geom
}
return nil
}()
if attributes == nil && geometry == nil { return nil }
return AGSGraphic(geometry: geometry, symbol: nil, attributes: attributes)
}) else
{
completion?(nil)
return
}
completion?(graphics)
}
// Uncomment this to see what's being sent…
// if let rc = AGSRequestConfiguration.global().copy() as? AGSRequestConfiguration {
// rc.debugLogRequests = true
// rc.debugLogResponses = true
// operation.requestConfiguration = rc
// }
AGSOperationQueue.shared().addOperation(operation)
} Note line 41 where I don't have to provide a credential because I'm using AGSJSONRequestOperation and if we've already authenticated for that service URL with Runtime, it'll use that credential automatically. And I've been a bit lazy in terms of returning errors, and the layer is hardcoded, but you can call this with something like this: doQuery(mapPoint: mapPoint, fields: ["OBJECTID", "NAME"]) { graphics in
guard let graphics = graphics else {
print("Something went wrong during the query")
return
}
print("Got \(graphics.count) graphics back!")
} Hope this helps!
... View more
11-21-2019
09:15 PM
|
1
|
2
|
2509
|
|
POST
|
Sounds good. Note that you could use AGSRequestOperation or AGSJSONRequestOperation which can make use of cached credentials (or you can assign a credential to it). The former returns data, the latter a JSON Dictionary. Here's an example, albeit not relying on credentials (in fact, I'm using it to get credentials). But in short, you create the operation, register a listener to handle the result, and then add it to the shared AGSOperationQueue. It uses NSURLSession behind the scenes and is a handy shortcut for many scenarios.
... View more
11-21-2019
07:09 PM
|
0
|
0
|
2509
|
|
POST
|
I'm really sorry, Todd. I was wrong. Even if we use the work-around to only populate the fields we want, internally Runtime loads all the fields up front and this is tripping up SQLite (which defaults to a limit of 2000). I can't think of a way to do this. I had hoped that using ArcGIS Online views would work, but they require that you have a source service that has the word FeatureServer in the name. The layer is failing to load because there are too many features fields, and then you're getting the context error. I'll keep digging, but it may not be possible for Runtime to do this right now. I do think the error could be better (we have done some work on improving this message elsewhere but it looks like this workflow wasn't considered).
... View more
11-21-2019
04:46 PM
|
0
|
5
|
2509
|
| Title | Kudos | Posted |
|---|---|---|
| 2 | 05-14-2026 07:07 AM | |
| 2 | 04-30-2026 10:59 AM | |
| 4 | 04-22-2026 08:07 AM | |
| 1 | 01-29-2026 09:39 AM | |
| 1 | 12-17-2025 10:12 AM |