|
POST
|
Hi Nick, Found the cause of the problem that is convert minutes to decimal degrees where I was using 3 (probably a typo) in the second offsetBy. Now after changed to 2 things start working as expected!!! /// Lat stringValue Format XXYY.ZZZZ -> XX° + (YY.ZZZZ / 60)° func convertLatitudeToDegree(with stringValue: String) -> Double { return Double(stringValue.substring(to: stringValue.index(stringValue.startIndex, offsetBy: 2)))! + Double(stringValue.substring(from: stringValue.index(stringValue.startIndex, offsetBy: 2)))! / 60 } I think my problem has been resolved. Thank you very much for your help. Cheers, Shimin
... View more
05-23-2018
06:24 PM
|
2
|
1
|
2416
|
|
POST
|
Hi Nick, Sorry Nick. Checked again and the coordinate does not look correct at all. It is NOT my current location! Something went wrong with my calculation from nmea string to lat and lon. I'm checking now and will report back here. Thanks you very much for picking this up!!! Shimin
... View more
05-23-2018
05:29 PM
|
0
|
2
|
2416
|
|
POST
|
Hi Nick, it looks ok to me. I think the 4326 is WGS84. Thanks, Shimin
... View more
05-23-2018
05:09 PM
|
0
|
3
|
2416
|
|
POST
|
Hi Nick, Tried your suggestion but it did not help: DispatchQueue.main.async { self.delegate.locationDisplayDataSource(self, didUpdateWith: agsLocation) self.delegate.locationDisplayDataSource(self, didUpdateWithHeading: rmcData.course) } Below are the sessionController codes and hopefully it'll help figure out the problem. Thank you Shimin /* Copyright (C) 2016 Bad Elf, LLC. All Rights Reserved. See LICENSE.txt for this sample’s licensing information Abstract: Controller for managing connected accessory and communicating with the accessory via NSInput & NSOutput streams. */ import UIKit import ExternalAccessory // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l < r case (nil, _?): return true default: return false } } // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func >= <T : Comparable>(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l >= r default: return !(lhs < rhs) } } // FIXME: comparison operators with optionals were removed from the Swift Standard Libary. // Consider refactoring the code to use the non-optional operators. fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): return l > r default: return rhs < lhs } } class SessionController: NSObject, EAAccessoryDelegate, StreamDelegate { static let sharedController = SessionController() var _accessory: EAAccessory? var _session: EASession? var _protocolString: String? var _writeData: NSMutableData? var _readData: NSMutableData? var _dataAsString: NSString? // MARK: Controller Setup func setupController(forAccessory accessory: EAAccessory, withProtocolString protocolString: String) { _accessory = accessory _protocolString = protocolString } // MARK: Opening & Closing Sessions func openSession() -> Bool { _accessory?.delegate = self _session = EASession(accessory: _accessory!, forProtocol: _protocolString!) if _session != nil { _session?.inputStream?.delegate = self _session?.inputStream?.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) _session?.inputStream?.open() _session?.outputStream?.delegate = self _session?.outputStream?.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) _session?.outputStream?.open() } else { print("Failed to create session") } return _session != nil } func closeSession() { _session?.inputStream?.close() _session?.inputStream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) _session?.inputStream?.delegate = nil _session?.outputStream?.close() _session?.outputStream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode) _session?.outputStream?.delegate = nil _session = nil _writeData = nil _readData = nil } // MARK: Write & Read Data func writeData(_ data: Data) { if _writeData == nil { _writeData = NSMutableData() } _writeData?.append(data) self.writeData() } func readData(_ bytesToRead: Int) -> Data { var data: Data? if _readData?.length >= bytesToRead { let range = NSMakeRange(0, bytesToRead) data = _readData?.subdata(with: range) _readData?.replaceBytes(in: range, withBytes: nil, length: 0) } return data! } func readBytesAvailable() -> Int { return (_readData?.length)! } // MARK: - Helpers func updateReadData() { let bufferSize = 128 var buffer = [UInt8](repeating: 0, count: bufferSize) while _session?.inputStream?.hasBytesAvailable == true { let bytesRead = _session?.inputStream?.read(&buffer, maxLength: bufferSize) if _readData == nil { _readData = NSMutableData() } _readData?.append(buffer, length: bytesRead!) _dataAsString = NSString(bytes: buffer, length: bytesRead!, encoding: String.Encoding.utf8.rawValue) NotificationCenter.default.post(name: Notification.Name(rawValue: "EXGPSSessionDataReceivedNotification"), object: nil) } } fileprivate func writeData() { while _session?.outputStream?.hasSpaceAvailable == true && _writeData?.length > 0 { var buffer = [UInt8](repeating: 0, count: _writeData!.length) _writeData?.getBytes(&buffer, length: (_writeData?.length)!) let bytesWritten = _session?.outputStream?.write(&buffer, maxLength: _writeData!.length) if bytesWritten == -1 { print("Write Error") return } else if bytesWritten > 0 { _writeData?.replaceBytes(in: NSMakeRange(0, bytesWritten!), withBytes: nil, length: 0) } } } // MARK: - EAAcessoryDelegate func accessoryDidDisconnect(_ accessory: EAAccessory) { // Accessory diconnected from iOS, updating accordingly } // MARK: - NSStreamDelegateEventExtensions func stream(_ aStream: Stream, handle eventCode: Stream.Event) { switch eventCode { case Stream.Event(): break case Stream.Event.openCompleted: break case Stream.Event.hasBytesAvailable: // Read Data updateReadData() break case Stream.Event.hasSpaceAvailable: // Write Data self.writeData() break case Stream.Event.errorOccurred: break case Stream.Event.endEncountered: break default: break } } }
... View more
05-23-2018
04:07 PM
|
1
|
10
|
2416
|
|
POST
|
Hi Nick, Thanks for your reply. NMEA Parser is not a problem. I wrote a simple one and it works fine. But I'm having trouble making the custom datasource to work and hoping you can help me out. Here is my simple implementation of the custom datasource for trying it out. Sorry I'm still on 10.2.5. import UIKit import ExternalAccessory class FCNSWGPSLocationDataSource: NSObject, AGSLocationDisplayDataSource { var delegate: AGSLocationDisplayDataSourceDelegate! var error: Error! var isStarted = false var sessionController: SessionController! var accessory: EAAccessory? required public init(sessionController: SessionController) { self.sessionController = sessionController self.accessory = sessionController._accessory } func start() { NotificationCenter.default.addObserver(self, selector: #selector(sessionDataReceived), name: NSNotification.Name(rawValue: "EXGPSSessionDataReceivedNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accessoryDidDisconnect), name: NSNotification.Name.EAAccessoryDidDisconnect, object: nil) let sessionOpened = self.sessionController.openSession() self.isStarted = sessionOpened if sessionOpened { self.delegate.locationDisplayDataSourceStarted(self) } else { self.error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to open an EA session."]) self.delegate.locationDisplayDataSource(self, didFailWithError: self.error) } } func stop() { NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "EXGPSSessionDataReceivedNotification"), object: nil) NotificationCenter.default.removeObserver(self, name: NSNotification.Name.EAAccessoryDidDisconnect, object: nil) self.sessionController.closeSession() self.isStarted = false self.delegate.locationDisplayDataSourceStopped(self) } // MARK: - Session Updates func sessionDataReceived(_ notification: Notification) { if sessionController._dataAsString != nil { let rawNMEAString = sessionController._dataAsString! let nmeaSentences = rawNMEAString.components(separatedBy: "\r\n") var rmcData: RmcData! var ggaData: GgaData! var gsaData: GsaData! for nmeaSentence in nmeaSentences { if let nmeaData = NmeaParser.parseSentence(data: nmeaSentence) { if nmeaData.isKind(of: RmcData.self) { rmcData = nmeaData as! RmcData } else if nmeaData.isKind(of: GgaData.self) { ggaData = nmeaData as! GgaData } else if nmeaData.isKind(of: GsaData.self) { gsaData = nmeaData as! GsaData } } } if rmcData != nil { let coordinate = CLLocationCoordinate2D(latitude: rmcData.latitude, longitude: rmcData.longitude) //these are just for testing purposes and the real data should come from the GGA and GSA sentences let altitude = CLLocationDistance(0) let horizontalAccuracy = CLLocationAccuracy(0) let verticalAccuracy = CLLocationAccuracy(0) let clLocation = CLLocation(coordinate: coordinate, altitude: altitude, horizontalAccuracy: horizontalAccuracy, verticalAccuracy: verticalAccuracy, course: rmcData.course, speed: rmcData.speed, timestamp: rmcData.timeStamp) let agsLocation = AGSLocation(clLocation: clLocation) self.delegate.locationDisplayDataSource(self, didUpdateWith: agsLocation) self.delegate.locationDisplayDataSource(self, didUpdateWithHeading: rmcData.course) } } // MARK: - EAAccessory Disconnection func accessoryDidDisconnect(_ notification: Notification) { let disconnectedAccessory = notification.userInfo![EAAccessoryKey] if (disconnectedAccessory as AnyObject).connectionID == accessory?.connectionID { } } } In the above codes the sessionController is from Bad Elf sample app which communicates with the connected accessory to retrieve nmea string data. I think this bit works fine as I can get valid nmea sentences as shown below and the latitude and longitude creating the agsLocation object are correct: I create an instance of my custom datasource class and set the mapView.locationDisplay.dataSource with the instance. Then after the map fully loads, if I try to invoke the mapView.locationDisplay.startDataSource(), the map always zooms to the centre of the map and displays the gps symbol there, not the expected current location. I checked the mapView.locationDisplay.location and mapLocation() and they are all nil. Obviously the location update did not get to pass through to the location display. What did I do wrong? By the way, Collector does the correct thing with the same gps device! Thank you very much for your help. Shimin
... View more
05-23-2018
12:33 AM
|
1
|
15
|
2416
|
|
POST
|
Hi Nick, Thanks a lot for your reply. Yes I will be implementing the custom location display datasource. I figured out what to do after reading the relevant docs in the 100.2.1 which provide clearer explanations about the custom datasource than that in the 10.2.5. Your instructions here verified what I'm thinking to do and are much appreciated. I also had a look at the external gps receiver support of the Collector app. Basically we would like to achieve the similar functionalities in our apps. Did Collector implement the support in a similar way or do you have any suggestion? I'm currently looking at the gps hexadecimal string data/NMEA sentences. I haven't found a complete NMEA parser in Swift in the net and had the feeling I have to write it myself... Any advises please? Thanks, Shimin
... View more
05-15-2018
04:09 PM
|
0
|
17
|
11613
|
|
POST
|
Hi Patrick, Many thanks for your reply which at least gives me something to think and I'm sorry to bother you. That's totally ok as nobody would remember things in detail that happened five years ago. Thank you again. Cheers, Shimin
... View more
05-09-2018
03:36 PM
|
0
|
0
|
786
|
|
POST
|
Hi Patrick, How did you implement your custom location display datasource? I'm also looking for implementing a custom datasource to force my apps to use location feeds from external gps devices. Would it be possible to give me any advices on this or share any ideas and codes please? Thanks, Shimin
... View more
05-08-2018
10:55 PM
|
0
|
2
|
786
|
|
POST
|
Hi Divesh, Just found out that our users have already tried what you suggested pairing Bad Elf GPS receivers and their devices and had the problem of iOS switching location feeds from external GPS receiver and the in-built sensor randomly. They want to be able to control the source of location feeds. Looks like the custom location datasource is the way for me to go... Cheers, Shimin
... View more
05-06-2018
08:06 PM
|
0
|
0
|
11611
|
|
POST
|
Hi Divesh, Thank you very much for your reply. I'm getting a Bad Elf GPS Unit and will be testing that out. The Bad Elf GPS has its own sdk/api to pull so much info from the receiver. I think soon or later I will be asked to read other extended infos from the receiver and in this case I will have to implement a custom location datasource as you suggested. I think I also have another use case of custom location datasource: location feed from drones. One of our projects looks at getting drone location and drone status (pitch, roll, yaw and gimbal) using the drone api. It was proposed that the project/app transmit the location data obtained from drones to an ad-hoc peer to peer wireless connected device using the Apple's MultiPeer Connectivity framework. Thus our apps will need to use the location feeds from drones and that's why I'm thinking the custom location datasource class... Any advises on this as well please? So any advises on how to implement a custom location datasource or directions leading to any documentation about it would be much appreciated, and everyone please? Cheers, Shimin
... View more
05-06-2018
04:57 PM
|
0
|
0
|
11611
|
|
POST
|
Hi All, I have a new requirement for our apps to use external gps location updates (Bad Elf GPS Unit) besides the default Apple location service updates. I understood that I need to create a custom location display data source class that conforms to the protocol <AGSLocationDisplayDataSource> and replace the mapVidw.locationDisplay.dataSource. My question is that: how do I implement the start method in the protocol of <AGSLocationDisplayDataSource>? I'm currently still on 10.2.5 and later will look at the 100.2.1 as well. Can anyone please shed any light on this or have any working code to share? Many thanks for your help. Cheers, Shimin
... View more
05-03-2018
10:02 PM
|
2
|
34
|
19538
|
|
POST
|
Many thanks Mark. Looking forward to it. Cheers, Shimin
... View more
04-29-2018
03:48 PM
|
1
|
0
|
1166
|
|
POST
|
Hi Mark, Just wondering if there is any chance to have a TOC sample in the SDK 100.2.1? Thanks, Shimin
... View more
04-26-2018
11:50 PM
|
0
|
2
|
1166
|
|
POST
|
Hi Nicholas, It worked !!! Thank you very much. Cheers, Shimin
... View more
04-13-2018
04:23 PM
|
0
|
0
|
626
|
|
POST
|
Hi all, I'm using SDK100.2.1 and adding runtime content layers (*.geodatabase file created in ArcMap and stored locally on device) to the map as shown in the code below. I found that the geodatabaseFeatureTables.count is always zero and as a result no layers are added to the map. Is this the correct way adding the runtime content layers to the map in 100.2.1? I'm able to do this in SDK 10.2.5. I'm using the free license level Lite. Thanks in advance. Shimin let baseMap = AGSBasemap(baseLayer: self.baseLayer) let map = AGSMap(basemap: baseMap) let gdb = AGSGeodatabase(fileURL: URL(fileURLWithPath: gdbFilePath) print(gdb.geodatabaseFeatureTables.count) for gdbFeatureTable: AGSGeodatabaseFeatureTable in gdb.geodatabaseFeatureTables { if gdbFeatureTable.hasGeometry { let gdbFeatureLayer = AGSFeatureLayer(featureTable: gdbFeatureTable) map.operationalLayers.add(gdbFeatureLayer) } } self.mapView.map = map
... View more
04-13-2018
12:38 AM
|
0
|
2
|
743
|
| Title | Kudos | Posted |
|---|---|---|
| 1 | 11-08-2022 01:10 PM | |
| 1 | 09-19-2022 09:21 PM | |
| 1 | 05-23-2022 06:49 PM | |
| 1 | 03-24-2022 05:49 PM | |
| 1 | 10-31-2021 03:16 PM |
| Online Status |
Offline
|
| Date Last Visited |
11-29-2025
01:58 AM
|