I need to copy a local variable tuple (sortedMarkers) from the function closestMarker() into the global variable sortedMarkerGlobal, however it doesn't seem to be working thus far. I want to use sortedMarkersGlobal in another function but it's count is zero (empty).
class MapViewController: UIViewController {
#IBOutlet weak var mapView: GMSMapView!
var sortedMarkerGlobal: [(lat: String, long: String)] = []
var nextParkCount: Int = 1 // Used to iterate over sortedMarkers with nextPark IBAction button
let locationManager = CLLocationManager()
var lastLocation: CLLocation? = nil // Create internal value to store location from didUpdateLocation to use in func showDirection()
var isAnimating: Bool = false
var dropDownViewIsDisplayed: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
....
}
func closestMarker(userLat: Double, userLong: Double)-> [(lat: String, long: String)] {
/*
var lengthRow = firstRow.count
while (sortedMarkers.count != lengthRow) { // There are markers that haven't been added to sortedMarkers
// Haversine formula
// add nearest marker to sortedMarkers
// Remove recently added marker from ParseViewControler() type tuple
// Recurse until all markers are in sortedMarkers
}
*/
let markerToTest = ParseViewController()
let firstRow = markerToTest.returnParse()
let lat2 = degreesToRadians(userLat) // Nearest lat in radians
let long2 = degreesToRadians(userLong) // Nearest long in radians
let R = (6371).doubleValue // Radius of earth in km
var closestLat: Double? = nil // Used to store the latitude of the closest marker
var closestLong: Double? = nil // Used to store the longitude of the closest marker
//
var indexToDelete: Int = 0 // Used to delete the marker which has been added to sortedMarkers tuple
let lengthRow = firstRow.count
var latLongs: (String, String)
var sortedMarkers: [(lat: String, long: String)] = []
var dynamicRow = firstRow // Tuple that has markers deleted from it
while (sortedMarkers.count != lengthRow) { // Store markers from closest to furtherst distance from user
var shortestDistance = 100.00E100
for (var i = 0; i < dynamicRow.count; i++) {
let testMarker = dynamicRow[i]
let testLat = (testMarker.lat as NSString)
let testLong = (testMarker.long as NSString)
let doubleLat = Double(testLat as String)
let doubleLong = Double(testLong as String)
let lat1 = degreesToRadians(doubleLat!)
let long1 = degreesToRadians(doubleLong!)
let dLat = lat2 - lat1
let dLong = long2 - long1
let a = ((sin((dLat)/2)) * (sin((dLat)/2))) + (cos(lat1)*cos(lat2)*((sin((dLong)/2)) * (sin((dLong)/2))))
let b = sqrt(a)
let d = (2*R) * (asin(b)) // Haversine formula
if (d < shortestDistance) {
closestLat = (doubleLat)
closestLong = (doubleLong)
shortestDistance = d
indexToDelete = i// Keep updating index of closest marker to later be removed
}
}
latLongs = (String(closestLat!), String(closestLong!)) // Each time for loop will find closest marker ~ NOT WORKING ~
sortedMarkers.append(latLongs)
dynamicRow.removeAtIndex(indexToDelete) // Remove marker that has just been added
}
sortedMarkerGlobal = sortedMarkers
return sortedMarkers
}
sortedMarkerGlobal is not a global variable. currently it is an instance variable like any of the other variables you have defined (locationManager, lastLocation, isAnimating, etc).
if you want it to be a global variable you should define it outside of your class MapViewController.
another option would be to make it a class variable of MapViewController by defining it as:
class MapViewController: UIViewController {
static var sortedMarkerGlobal: [(lat: String, long: String)] = []
}
Well I tried to test your code in playground and it worked for me
import UIKit
class Test {
var global:[(lat: String, long: String)] = []
func test1() {
var sortedMarkers: [(lat: String, long: String)] = []
var latLongs: (String, String)
latLongs = ("123", "123")
sortedMarkers.append(latLongs)
global = sortedMarkers
}
}
let test:Test = Test()
test.test1();
print("global \(test.global)")
And the result is global [("123", "123")] so I guess it should also work for you, the problem might be somewhere else.
I would suggest that you just use a closures instead of saving to a global variable. This lets you pass in the parameters and return your result based on that. You can save that result in the function that called it and do what ever you want from there.
Related
I have a class which has those data
class Events {
var name: String!
var latitude: Double!
var longitude: Double!
}
And I fill it out with data from json.
So some Events have the same lat and lon but they are not on continuous, i mean its not that event3 is the same as event4 etc.
So I'm trying to show them on a map
Filling out this array
var events = [Events]()
And in this for loop i'm making the pins.
for events in events {
let annotation = MKPointAnnotation()
annotation.title = events.name
annotation.coordinate = CLLocationCoordinate2D(latitude: events.latitude, longitude: events.longitude)
mapView.addAnnotation(annotation)
}
How can I make a quick search before deploying the pins, to see if a pin has the same lat and lon with another pin, to add some digits just to show them both close?
Thanks a lot!
Use Set to find unique instances. In order to use Set your base element, Events in this case must be Hashable and by implication Equatable:
class Events : Hashable {
var name: String!
var latitude: Double!
var longitude: Double!
// implement Hashable
var hashValue: Int {
return latitude.hashValue | longitude.hashValue
}
// Implement Equatable
static func ==(lhs:Events, rhs:Events) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}
Then your main loop is a direct extension of what you already have, note that this collapses all matches down to a single point and changes the name to indicate how many matches there are:
// Use a Set to filter out duplicates
for event in Set<Events>(events) {
let annotation = MKPointAnnotation()
// Count number of occurrences of each item in the original array
let count = events.filter { $0 == event }.count
// Append (count) to the title if it's not 1
annotation.title = count > 1 ? "\(event.name) (\(count))" : event.name
// add to the map
}
If, instead, you want to move the points so that they don't stack up, then you want something like, where we build up the set of occupied locations as we go and mutate the points to move them a little.
func placeEvents(events:[Events], mapView:MKMapView) {
var placed = Set<Events>()
for event in events {
if placed.contains(event) {
// collision: mutate the location of event as needed,
}
// Add the mutated point to occupied points
placed.formUnion([event])
// Add the point to the map here
}
}
If the values aren't expected to be exactly the same, but only within, eg., .0001 of each other, then you could use the following for hashValue and ==
fileprivate let tolerance = 1.0 / 0.0001
private var tolerantLat : Long { return Long(tolerance * latitude) }
private var tolerantLon : Long { return Long(tolerance * longitude) }
var hashValue : Int {
return tolerantLat.hashValue | tolerantLon.hashValue
}
static func ==(lhs:Events, rhs:Events) -> Bool {
return lhs.tolerantLat == rhs.tolerantLat && lhs.tolerantLon == rhs.tolerantLon
}
i am using this project to generate my heatmap:
https://github.com/dataminr/DTMHeatmap
I integrated the code as stated in:
https://github.com/dataminr/DTMHeatmap/issues/1
from #johndpope:
https://github.com/johndpope/TranSafe
First, it is compiled successfully, but when i use the "readData" like this:
readData([[52.517138, 13.401489], [52.517137, 13.401488], [52.517136, 13.401487]])
i get the
Thread 1: EXC_BAD_ACCESS(code=2, address=blabla) error
here is the method:
func readData(_ array: [[Double]]){
self.heatmap = DTMHeatmap()
var dict = Dictionary<NSObject, AnyObject>();
for entry in array{
let coordinate = CLLocationCoordinate2D(latitude: entry[1], longitude: entry[0]);
let mapPoint = MKMapPointForCoordinate(coordinate)
let type = NSValue(mkCoordinate: coordinate).objCType // <- THIS IS IT
let value = NSValue(bytes: Unmanaged.passUnretained(mapPoint as AnyObject).toOpaque(), objCType: type);
dict[value] = 1 as AnyObject?;
}
self.heatmap.setData(dict as [AnyHashable: Any]);
self.mapView.add(self.heatmap)
}
func MKMapPointForCoordinate(_ coordinate: CLLocationCoordinate2D) -> MKMapPoint {
return MKMapPointForCoordinate(coordinate);
}
// etc ...
I have really no idea what i have done wrong, anybody could help me with this issue?
As far as I can read from the original code of DTMHeatmap, the keys for the dictionary passed for setData need to be NSValues containing MKMapPoint. And the code you have shown is not a proper code to make such NSValues. (I really doubt the original Swift 2 code you have found would actually work..., MKMapView cannot be bridged to Objective-C object in Swift 2, so mapPoint as! AnyObject should always fail.)
The readData method should be something like this:
func readData(_ array: [[Double]]){
self.heatmap = DTMHeatmap()
var dict: [AnyHashable: Any] = [:]
for entry in array{
let coordinate = CLLocationCoordinate2D(latitude: entry[1], longitude: entry[0]);
var mapPoint = MKMapPointForCoordinate(coordinate)
//Creating `objCType` manually is not recommended, but Swift does not have `#encoding()`...
let type = "{MKMapPoint=dd}"
let value = NSValue(bytes: &mapPoint, objCType: type)
dict[value] = 1
}
self.heatmap.setData(dict)
self.mapView.add(self.heatmap)
}
(I haven't checked with the actual DTMHeatmap, so you may need some more fixes.)
Very performant heatmap library. I managed to get this working and it renders nicely. I am adding this answer because it also includes the rendererFor overlay delegate function needed to work.
class HeatmapViewController: UIViewController, MKMapViewDelegate {
var heatmap: DTMHeatmap? = nil
var diffHeatmap: DTMDiffHeatmap? = nil
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
var ret: [AnyHashable: Any] = [:]
for location in locations {
var mapPoint = MKMapPointForCoordinate(location.coordinate)
let mapPointValue = NSValue(bytes: &mapPoint, objCType: "{MKMapPoint=dd}")
ret[mapPointValue] = 10.0 // weight
}
self.heatmap = DTMHeatmap()
self.heatmap?.setData(ret)
self.mapView.delegate = self; // Important
self.mapView.add(self.heatmap!)
}
}
This part is very important and for this to work you need to set the mapView's delegate to self.
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
return DTMHeatmapRenderer(overlay: overlay)
}
I have a option string and want to convert that to double.
this worked in Swift 2 , but since converted to Swift 3, I am getting value of 0.
var dLati = 0.0
dLati = (latitude as NSString).doubleValue
I have check and latitude has a optional string value of something like -80.234543218675654 , but dLati value is 0
*************** ok, new update for clarity *****************
I have a viewcontroller which i have a button in it, and when the button is touched, it will call another viewcontroller and pass a few values to it
here is the code for the first viewcontroller
var currentLatitude: String? = ""
var currentLongitude: String? = ""
var deviceName = ""
var address = ""
// somewhere in the code, currentLatitude and currentLongitude are get set
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "map" {
let destViewController : MapViewController = segue.destination as! MapViewController
print(currentLongitude!) // Print display: Optional(-80.192279355363768)
print(currentLatitude!) // Print display: Optional(25.55692663937162)
destViewController.longitude = currentLongitude!
destViewController.latitude = currentLatitude!
destViewController.deviceName = deviceName
destViewController.address = address
}
}
Here is the code for the second view controller called MapViewController
var longitude: String? = " "
var latitude: String? = ""
.
.
override func viewDidLoad() {
if let lat = latitude {
print(lat) // Print display: optiona(25.55692663937162)
dLati = (lat as NSString).doubleValue
print(dLati) // Print display: 0.0
}
.
.
}
Thanks
Borna
A safe way to achieve this without needing to use Foundation types is using Double's initializer:
if let lat = latitude, let doubleLat = Double(lat) {
print(doubleLat) // doubleLat is of type Double now
}
Unwrap the latitude value safely and then use
var dLati = 0.0
if let lat = latitude {
dLati = (lat as NSString).doubleValue
}
let dLati = Double(latitude ?? "") ?? 0.0
This code works fine.
var dLati = 0.0
let latitude: String? = "-80.234543218675654"
if let strLat = latitude {
dLati = Double(strLat)!
}
You can do this simply in one line.
var latitude: Double = Double("-80.234543218675654") ?? 0.0
This creates a variable named latitude that is of type Double that is either instantiated with a successful Double from String or is given a fallback value of 0.0
When you get a string with double value something like this
"Optional(12.34567)"
You can use a Regex which takes out the double value from the string.
This is the example code for a Regex if the string is "Optional(12.34567)":
let doubleLatitude = location.latitude?.replacingOccurrences(of: "[^\\.\\d+]", with: "", options: [.regularExpression])
Actually the word optional was part of the string. Not sure how it got added in the string? But the way I fixed it was like this. latitude was this string "Optional(26.33691567239162)" then I did this code
let start = latitude.index(latitude.startIndex, offsetBy: 9)
let end = latitude.index(latitude.endIndex, offsetBy: -1)
let range = start..<end
latitude = latitude.substring(with: range)
and got this as the final value
26.33691567239162
Don´t convert it to an NSString, you can force it to a Double but have a fallback if it fails. Something like this:
let aLat: String? = "11.123456"
let bLat: String? = "11"
let cLat: String? = nil
let a = Double(aLat!) ?? 0.0 // 11.123456
let b = Double(bLat!) ?? 0.0 // 11
let c = Double(cLat!) ?? 0.0 // 0
So in your case:
dLati = Double(latitude!) ?? 0.0
Update:
To handle nil values do the following (note that let cLat is nil:
// Will succeed
if let a = aLat, let aD = Double(aLat!) {
print(aD)
}
else {
print("failed")
}
// Will succeed
if let b = bLat, let bD = Double(bLat!) {
print(bD)
}
else {
print("failed")
}
// Will fail
if let c = cLat, let cD = Double(cLat!) {
print(cD)
}
else {
print("failed")
}
In swift 3.1, we can combine extensions and Concrete Constrained Extensions
extension Optional where Wrapped == String
{
var asDouble: Double
{
return NSString(string: self ?? "").doubleValue
}
}
Or
extension Optional where Wrapped == String
{
var asDouble: Double
{
return Double(str ?? "0.00") ?? 0.0
}
}
Swift 4
let YourStringValue1st = "33.733322342342" //The value is now in string
let YourStringValue2nd = "73.449384384334" //The value is now in string
//MARK:- For Testing two Parameters
if let templatitude = (YourStringValue1st as? String), let templongitude = (YourStringValue2nd as? String)
{
movetosaidlocation(latitude: Double(templat)!, longitude: Double(templong)!, vformap: cell.vformap)
}
let YourStringValue = "33.733322342342" //The value is now in string
//MARK:- For Testing One Value
if let tempLat = (YourStringValue as? String)
{
let doublevlue = Double(tempLat)
//The Value is now in double (doublevlue)
}
I need to copy a local variable tuple (sortedMarkers) from the function closestMarker() into the global variable sortedMarkerGlobal, however it doesn't seem to be working thus far. I want to use sortedMarkersGlobal in another function but it's count is zero (empty).
class MapViewController: UIViewController {
#IBOutlet weak var mapView: GMSMapView!
var sortedMarkerGlobal: [(lat: String, long: String)] = []
var nextParkCount: Int = 1 // Used to iterate over sortedMarkers with nextPark IBAction button
let locationManager = CLLocationManager()
var lastLocation: CLLocation? = nil // Create internal value to store location from didUpdateLocation to use in func showDirection()
var isAnimating: Bool = false
var dropDownViewIsDisplayed: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
....
}
func closestMarker(userLat: Double, userLong: Double)-> [(lat: String, long: String)] {
/*
var lengthRow = firstRow.count
while (sortedMarkers.count != lengthRow) { // There are markers that haven't been added to sortedMarkers
// Haversine formula
// add nearest marker to sortedMarkers
// Remove recently added marker from ParseViewControler() type tuple
// Recurse until all markers are in sortedMarkers
}
*/
let markerToTest = ParseViewController()
let firstRow = markerToTest.returnParse()
let lat2 = degreesToRadians(userLat) // Nearest lat in radians
let long2 = degreesToRadians(userLong) // Nearest long in radians
let R = (6371).doubleValue // Radius of earth in km
var closestLat: Double? = nil // Used to store the latitude of the closest marker
var closestLong: Double? = nil // Used to store the longitude of the closest marker
//
var indexToDelete: Int = 0 // Used to delete the marker which has been added to sortedMarkers tuple
let lengthRow = firstRow.count
var latLongs: (String, String)
var sortedMarkers: [(lat: String, long: String)] = []
var dynamicRow = firstRow // Tuple that has markers deleted from it
while (sortedMarkers.count != lengthRow) { // Store markers from closest to furtherst distance from user
var shortestDistance = 100.00E100
for (var i = 0; i < dynamicRow.count; i++) {
let testMarker = dynamicRow[i]
let testLat = (testMarker.lat as NSString)
let testLong = (testMarker.long as NSString)
let doubleLat = Double(testLat as String)
let doubleLong = Double(testLong as String)
let lat1 = degreesToRadians(doubleLat!)
let long1 = degreesToRadians(doubleLong!)
let dLat = lat2 - lat1
let dLong = long2 - long1
let a = ((sin((dLat)/2)) * (sin((dLat)/2))) + (cos(lat1)*cos(lat2)*((sin((dLong)/2)) * (sin((dLong)/2))))
let b = sqrt(a)
let d = (2*R) * (asin(b)) // Haversine formula
if (d < shortestDistance) {
closestLat = (doubleLat)
closestLong = (doubleLong)
shortestDistance = d
indexToDelete = i// Keep updating index of closest marker to later be removed
}
}
latLongs = (String(closestLat!), String(closestLong!)) // Each time for loop will find closest marker ~ NOT WORKING ~
sortedMarkers.append(latLongs)
dynamicRow.removeAtIndex(indexToDelete) // Remove marker that has just been added
}
sortedMarkerGlobal = sortedMarkers
return sortedMarkers
}
I have a function but i don´t know assign the return to a var or let
func getCoords() -> String{
var coords = "parameter"
return coords
}
i try this but not works
var result = getCoords()
I try both codes blow,both of them works with my playground.
func getCoords() -> String{
var coords = "parameter"
return coords
}
var string = getCoords()
string = "abc" + string//abcparameter
And
func getCoords() -> String{
let coords = "parameter"
return coords
}
var string = getCoords()
string = "abc" + string//abcparameter
Swift is a language pass by copy,so I think if you want a let or var.You can use this function like this.
let string = getCoords()//Get a let
var string = getCoords()//Get a var