1

Updating previous question for better explanation.

var breadcrumbs: [CLLocationCoordinate2D] = []
var path: [CLLocationCoordinate2D] = []

This is called every 10 seconds to append a CLLocationCoordinate2D to the array.

func addBreadcrumb(){
    let speed = (locationmanager.location?.speed)!
    let speedRounded = speed.roundTo(places: 4)
    let crumbCoordinate = locationmanager.location?.coordinate

    breadcrumbs.append(crumbCoordinate!)
    tripSpeeds.append(speedRounded)
}

Once the user is done with their trip they tap a button and the following function is called. This gets the data in to firebase. I am saving as Strings as I get an error if done otherwise.

func submitTripPath(){
    let tripID: String? = tripUID
    let tripPath = String(describing: breadcrumbs) //This may be the problem
    let speeds = String(describing: tripSpeeds)

    var ref: FIRDatabaseReference!
    ref = FIRDatabase.database().reference()
    let tripRef = ref.child("TripPaths").child(tripID!)
    let tripDictionary = ["routePath" : tripPath, "routeSpeed" : speeds] as [String : Any]
    tripRef.updateChildValues(tripDictionary) { (err, ref) in
        if err != nil {
            print(err!)
            return
        }
    }
}

In another screen I successfully pull out the String of Coordinates in a Firebase Database Reference.

let routePath = dict["routePath"] as! String //This may also be an issue
//output looks like this
"[__C.CLLocationCoordinate2D(latitude: 37.337728550000001, longitude: -122.02796406), __C.CLLocationCoordinate2D(latitude: 37.337716899999997, longitude: -122.02835139), __C.CLLocationCoordinate2D(latitude: 37.337694319999997, longitude: -122.0287719)]"

I want to be able to use this as an array from CLLocationCoordinate2D to draw a polyline using the following. I am having trouble converting this string to a usable array of CLLocationCoordinate2D.

    if (path.count > 1) {
        let sourceIndex = path.count - 1
        let destinationIndex = path.count - 2

        let c1 = path[sourceIndex]
        let c2 = path[destinationIndex]

        var a = [c1, c2]
        let polyline = MKPolyline(coordinates: &a, count: a.count)
        mapContainerView.add(polyline)
    }

If you have a suggestion on betting saving the original array, pulling it from Firebase, or converting the string please let me know. If you need other code for reference, please let me know.

AL.
  • 36,815
  • 10
  • 142
  • 281
Eric.18
  • 463
  • 4
  • 20
  • Possible duplicate of [Convert String to CLLocationCoordinate2D in swift](https://stackoverflow.com/questions/28417512/convert-string-to-cllocationcoordinate2d-in-swift) – shallowThought May 24 '17 at 16:26
  • I don't think this is a duplicate as I have a string of many CLLocationCoordiante2D in a single string, not a string for each... which I could pull the lat/lng from. I am already doing that with individual annotations. If you see how I can apply that answer to my question can you explain further? – Eric.18 May 24 '17 at 16:30
  • How was that string created in the first place? It would be better to come up with a better encoding method to begin with. – rmaddy May 24 '17 at 16:35
  • @rmaddy in another screen a user's path is tracked, an array of CLLocationCoordiante2D is created and saved in firebase as a String. If I try to save it as the array I get an error: Terminating app due to uncaught exception 'InvalidFirebaseData', reason: '(updateChildValues:withCompletionBlock:) Cannot store object of type NSConcreteValue at 0. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.' – Eric.18 May 24 '17 at 16:38
  • I get that it is a string but there are much better ways to convert an array of coordinates to a string that will make it much easier to convert the string back into an array of coordinates. Your current method to create the string makes it very difficult to go back. I would redo your question that focuses more on the best way to make the round trip conversion of an array of coordinates through Firebase. Include your current relevant code for creating the string and your attempts to decode the string. – rmaddy May 24 '17 at 16:47
  • @rmaddy I updated the question to hopefully make it better. – Eric.18 May 24 '17 at 17:05
  • You might also want to scan through [these relevant search results](https://stackoverflow.com/search?q=%5Bswift%5D+CLLocationCoordinate2D+firebase). – rmaddy May 24 '17 at 17:13

1 Answers1

5

So I think your biggest problem is going to be that you are using String(describing:). This is going to add those weird class names that you are seeing. You are better off coming up with your own encoding method for lat/long and then reverse encoding it to get back your location data. So something like the following would be a bette approach.

func submitTripPath(){
    let tripID: String? = tripUID
    let tripPath = encodeCoordinates(coords: breadcrumbs)
    let speeds = String(describing: tripSpeeds)

    var ref: FIRDatabaseReference!
    ref = FIRDatabase.database().reference()
    let tripRef = ref.child("TripPaths").child(tripID!)
    let tripDictionary = ["routePath" : tripPath, "routeSpeed" : speeds] as [String : Any]
    tripRef.updateChildValues(tripDictionary) { (err, ref) in
        if err != nil {
            print(err!)
            return
        }
    }
}

func encodeCoordinates(coords: [CLLocationCoordinate2D]) -> String {
    let flattenedCoords: [String] = coords.map { coord -> String in "\(coord.latitude):\(coord.longitude)" }
    let encodedString: String = flattenedCoords.joined(separator: ",")
    return encodedString
}

func decodeCoordinates(encodedString: String) -> [CLLocationCoordinate2D] {
    let flattenedCoords: [String] = encodedString.components(separatedBy: ",")
    let coords: [CLLocationCoordinate2D] = flattenedCoords.map { coord -> CLLocationCoordinate2D in
        let split = coord.components(separatedBy: ":")
        if split.count == 2 {
            let latitude: Double = Double(split[0]) ?? 0
            let longitude: Double = Double(split[1]) ?? 0
            return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
        } else {
            return CLLocationCoordinate2D()
        }
    }
    return coords
}

I just used commas and colons for my encoding but you can easily use something else.

Binary Platypus
  • 413
  • 2
  • 9
  • One thing to remember implementing this is that I did very little error checking in my example code. Especially in decode you might want to make sure the decode is always returning a valid CLLocationCoordiante2D and not just an empty init. – Binary Platypus May 24 '17 at 18:26
  • this looks interesting. It works great, getting the data into the database,but using the decodeCoordinates is not working as it shows an error of: cannot convert value of type 'String' to expected argument type 'NSCoder', coming from, let routePath = dict["routePath"] as! String in the firebase reference. Pulling it out as a NSCoder does not work either. – Eric.18 May 24 '17 at 19:06
  • 1
    You should just be able to do the following `let routePath: String = dict["routePath"] as! String path = decodeCoordinates(encodedString: routePath)` – Binary Platypus May 24 '17 at 19:09
  • What does the `print(routePath)` look like after getting it back out of the database? It should in theory look like "180:180,90:90". – Binary Platypus May 24 '17 at 19:11
  • Your answer works! Thanks for helping me out on this and sticking with me! I have accepted this answer. I believe this answer will help a lot of people in the future. – Eric.18 May 24 '17 at 19:18