Skip to content

Instantly share code, notes, and snippets.

@kgao
Last active April 25, 2020 03:57
Show Gist options
  • Save kgao/4d71ddad3554ff8024e8518e5a6fd293 to your computer and use it in GitHub Desktop.
Save kgao/4d71ddad3554ff8024e8518e5a6fd293 to your computer and use it in GitHub Desktop.
mac dev tips
code () { VSCODE_CWD="$PWD" open -n -b "com.microsoft.VSCode" --args $* ;}
///: A MapKit based Playground
import PlaygroundSupport
import UIKit
class ReverseGeocodingViewController: UIViewController {
// MARK: - Properties
@IBOutlet var latitudeTextField: UITextField!
@IBOutlet var longitudeTextField: UITextField!
@IBOutlet var geocodeButton: UIButton!
@IBOutlet var activityIndicatorView: UIActivityIndicatorView!
@IBOutlet var locationLabel: UILabel!
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
lazy var geocoder = CLGeocoder()
@IBAction func geocode(_ sender: UIButton) {
guard let latAsString = latitudeTextField.text, let lat = Double(latAsString) else { return }
guard let lngAsString = longitudeTextField.text, let lng = Double(lngAsString) else { return }
// Create Location
let location = CLLocation(latitude: lat, longitude: lng)
// Geocode Location
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
// Process Response
self.processResponse(withPlacemarks: placemarks, error: error)
}
// Update View
geocodeButton.isHidden = true
activityIndicatorView.startAnimating()
}
private func processResponse(withPlacemarks placemarks: [CLPlacemark]?, error: Error?) {
// Update View
geocodeButton.isHidden = false
activityIndicatorView.stopAnimating()
if let error = error {
print("Unable to Reverse Geocode Location (\(error))")
locationLabel.text = "Unable to Find Address for Location"
} else {
if let placemarks = placemarks, let placemark = placemarks.first {
locationLabel.text = placemark.compactAddress
} else {
locationLabel.text = "No Matching Addresses Found"
}
}
}
}
extension CLPlacemark {
var compactAddress: String? {
if let name = name {
var result = name
if let street = thoroughfare {
result += ", \(street)"
}
if let city = locality {
result += ", \(city)"
}
if let country = country {
result += ", \(country)"
}
return result
}
return nil
}
}
// Forward Geocoding With Clgeocoder
let geocoder = CLGeocoder()
let address = "8787 Snouffer School Rd, Montgomery Village, MD 20879"
geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error ?? "")
}
if let placemark = placemarks?.first {
let coordinates:CLLocationCoordinate2D = placemark.location!.coordinate
//print("Lat: \(coordinates.latitude) | Long: \(coordinates.longitude)")
}
})
// Specify the place data types to return place - likelyhood.
//let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
// UInt(GMSPlaceField.placeID.rawValue))!
//placesClient?.findPlaceLikelihoodsFromCurrentLocation(withPlaceFields: fields, callback: {
// (placeLikelihoodList: Array<GMSPlaceLikelihood>?, error: Error?) in
// if let error = error {
// print("An error occurred: \(error.localizedDescription)")
// return
// }
//
// if let placeLikelihoodList = placeLikelihoodList {
// for likelihood in placeLikelihoodList {
// let place = likelihood.place
// print("Current Place name \(String(describing: place.name)) at likelihood \(likelihood.likelihood)")
// print("Current PlaceID \(String(describing: place.placeID))")
// }
// }
//})
//async,wait
class AsyncOperation {
private let semaphore: DispatchSemaphore
private let dispatchQueue: DispatchQueue
typealias CompleteClosure = ()->()
init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) {
semaphore = DispatchSemaphore(value: numberOfSimultaneousActions)
dispatchQueue = DispatchQueue(label: dispatchQueueLabel)
}
func run(closure: @escaping (@escaping CompleteClosure)->()) {
dispatchQueue.async {
self.semaphore.wait()
closure {
self.semaphore.signal()
}
}
}
}
import MapKit
//--------------------------------------
// Draw on the map - local test:
let appleParkWayCoordinates = CLLocationCoordinate2DMake(40.7569, -73.9828)
// Now let's create a MKMapView
let mapView = MKMapView(frame: CGRect(x:0, y:0, width:800, height:800))
// Define a region for our map view
var mapRegion = MKCoordinateRegion()
let mapRegionSpan = 0.02
mapRegion.center = appleParkWayCoordinates
mapRegion.span.latitudeDelta = mapRegionSpan
mapRegion.span.longitudeDelta = mapRegionSpan
mapView.setRegion(mapRegion, animated: true)
// Create a map annotation
let annotation = MKPointAnnotation()
annotation.coordinate = appleParkWayCoordinates
annotation.title = "Apple Inc."
annotation.subtitle = "One Apple Park Way, Cupertino, California."
mapView.addAnnotation(annotation)
// Add the created mapView to our Playground Live View
PlaygroundPage.current.liveView = mapView
/// --------------------------------------------------------------------------
///
///
/// CovidSafe
///
///
/// ---------------------------------------- Coordinate <> Locationm --------------------------------------------
import CoreLocation
// draw on the map
func drawMap(lat:Double,lon:Double,address:CLPlacemark){
//--------------------- Draw on the map -----------------
let appleParkWayCoordinates = CLLocationCoordinate2DMake(lat, lon)
// Now let's create a MKMapView
let mapView = MKMapView(frame: CGRect(x:0, y:0, width:800, height:800))
// Define a region for our map view
var mapRegion = MKCoordinateRegion()
let mapRegionSpan = 0.02
mapRegion.center = appleParkWayCoordinates
mapRegion.span.latitudeDelta = mapRegionSpan
mapRegion.span.longitudeDelta = mapRegionSpan
mapView.setRegion(mapRegion, animated: true)
// Create a map annotation
let annotation = MKPointAnnotation()
annotation.coordinate = appleParkWayCoordinates
annotation.title = address.name! //Buiness name? Google API place likelyhood.
annotation.subtitle = address.compactAddress!
mapView.addAnnotation(annotation)
// Add the created mapView to our Playground Live View
PlaygroundPage.current.liveView = mapView
//--------------------------------------
}
//Test input:
let lat = 40.75
let lon = -73.98
var placeList: [CLPlacemark] = [] //List of CLPlacemarks
//Batch input:
struct Geo : Codable {
let lat : Double
let lon : Double
let timestamp: Double
}
struct AddressTS : Codable {
let address : String
let timestamp: Double
}
struct AddressPeriod : Codable {
let address: String
let period: String
}
//input type 1
let geoList = [Geo(lat:40.75,lon:-73.98,timestamp:1587749400),
Geo(lat:40.75,lon:-73.98,timestamp:1587751200),
Geo(lat:40.76,lon:-73.99,timestamp:1587752000),
Geo(lat:40.76,lon:-73.99,timestamp:1587762000),
Geo(lat:40.77,lon:-74.00,timestamp:1587770000)]
do {
//encode: struct to json
let jsonData = try JSONEncoder().encode(geoList)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString) // [{"lat":40.75,"lon":-73.98,"timestamp":1587749400},{"lat":40.75,"lon":-73.98,"timestamp":1587751200}]
//decode: json to struct
let decodedGeos = try JSONDecoder().decode([Geo].self, from: jsonData)
print(decodedGeos) //[__lldb_expr_166.Geo(lat: 40.75, lon: -73.98, timestamp: 1587749400), __lldb_expr_166.Geo(lat: 40.75, lon: -73.98, timestamp: 1587751200)]
//decode json string to struct
let JSON = """
[{ "lat":40.75,
"lon":-73.98,
"timestamp":1587749400
},
{
"lat":40.75,
"lon":-73.98,
"timestamp":1587751200
}]
"""
//input type 2
let test:[Geo] = try! JSONDecoder().decode([Geo].self, from: JSON.data(using: .utf8)!)
print(test[0].timestamp)
} catch { print(error) }
// Step 1: convert JSON to geoList input, then use the method - batchReserseGeocodeLocation:
func batchReserseGeocodeLocation(geoList:[Geo], completionHandler:@escaping([AddressTS]) -> ()){ //Please specify your type as contract to use
// Fire an asynchronous callback when all your requests finish in synchronous.
let asyncGroup = DispatchGroup()
var placeList: [AddressTS] = [AddressTS](repeating: AddressTS(address:"",timestamp:0), count: geoList.count) //List of AddressTS
for (index, geo) in geoList.enumerated()
{ asyncGroup.enter()
//Reverse Geocoding With Clgeocoder
let gecoder = CLGeocoder.init()
gecoder.reverseGeocodeLocation(CLLocation.init(latitude:geo.lat,longitude: geo.lon)) { (addresses, error) in
if error == nil{
if let address = addresses{
//Async: Here you can get all the info by combining that you can make address!
print("Address \(index) : \(address)")
//async - availability first
//placeList.append(AddressTS(address: address[0].compactAddress!, timestamp: geo.timestamp))
//async - sequential reserved - fixed size
placeList[index] = AddressTS(address: address[0].compactAddress!, timestamp: geo.timestamp)
//TODO: connect with frontend UI.
//drawMap(lat:lat,lon:lon,address:address[0])
asyncGroup.leave()
}
}
}
}
asyncGroup.notify(queue: .main){
print("Finished the loop - find \(geoList.count) addresses")
// action complete
completionHandler(placeList)
}
}
// UTC time converter
func UTC_Converter(unixtime1:Double, unixtime2:Double, timezone:String?=nil) -> String{
// Localization
var localTimeZoneAbbreviation: String { return TimeZone.current.abbreviation() ?? "" } // "UTC-4"
let date1 = Date(timeIntervalSince1970: unixtime1)
let date2 = Date(timeIntervalSince1970: unixtime2)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: localTimeZoneAbbreviation) //Set timezone that you want: i.e. New York is "UTC-4"
// Overwrite
if (timezone != nil) {
dateFormatter.timeZone = TimeZone(abbreviation: timezone ?? "")
}
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" //Specify your format that you want
let strDate1 = dateFormatter.string(from: date1)
let strDate2 = dateFormatter.string(from: date2)
return (strDate1 + " ~ " + strDate2)
}
// Step2: aggregate address result - from timestamp to period
func aggregateAddressList(addressTSList:[AddressTS], completionHandler:@escaping([AddressPeriod]) -> ()){
var AddressPeriodList: [AddressPeriod] = [] //List of AddressPeriod
var start: Double = 0 // UTC start
var end: Double = 0 // UTC end
var address = "" // address pointer
for (index, address_ts) in addressTSList.enumerated(){
if index == 0 { // first one
start = address_ts.timestamp
end = address_ts.timestamp
address = address_ts.address
}
else if address_ts.address != address {
//append - dynamic size
//logic: same location + 10min
if start == end {
AddressPeriodList.append(AddressPeriod(address:address,period: UTC_Converter(unixtime1:start,unixtime2:(start+600))))
} else {
AddressPeriodList.append(AddressPeriod(address:address,period: UTC_Converter(unixtime1:start,unixtime2:end)))
}
//update
start = address_ts.timestamp
end = address_ts.timestamp
address = address_ts.address
if index == (addressTSList.count-1){ // last one - unique, add extra
//logic: same location + 10min
AddressPeriodList.append(AddressPeriod(address:address_ts.address, period: UTC_Converter(unixtime1:start,unixtime2:(start+600))))
}
} else {//move on
end = address_ts.timestamp
if index == (addressTSList.count-1){ // last one - same, add extra
//logic: same location + 10min
if start == end {
AddressPeriodList.append(AddressPeriod(address:address, period: UTC_Converter(unixtime1:start,unixtime2:(start+600))))
} else {
AddressPeriodList.append(AddressPeriod(address:address, period: UTC_Converter(unixtime1:start,unixtime2:end)))
}
}
}
}
completionHandler(AddressPeriodList)
}
//Test - Type 1 input
batchReserseGeocodeLocation(geoList: geoList, completionHandler: { addressTSList in
sleep(1)
do{
try print("-------------------\nAddressTSList: \((String(data: JSONEncoder().encode(addressTSList), encoding: .utf8)!))")
}catch{ print(error) }
aggregateAddressList(addressTSList:addressTSList, completionHandler: { addressPeriodList in
sleep(1)
do{
try print("-------------------\nFinal AddressPeriodList: \((String(data: JSONEncoder().encode(addressPeriodList), encoding: .utf8)!))")
}catch{ print(error) }
})
})
@kgao
Copy link
Author

kgao commented Apr 25, 2020

[{"lat":40.75,"lon":-73.980000000000004,"timestamp":1587749400},{"lat":40.75,"lon":-73.980000000000004,"timestamp":1587751200},{"lat":40.759999999999998,"lon":-73.989999999999995,"timestamp":1587752000},{"lat":40.759999999999998,"lon":-73.989999999999995,"timestamp":1587762000},{"lat":40.770000000000003,"lon":-74,"timestamp":1587770000}]
[__lldb_expr_208.Geo(lat: 40.75, lon: -73.98, timestamp: 1587749400.0), __lldb_expr_208.Geo(lat: 40.75, lon: -73.98, timestamp: 1587751200.0), __lldb_expr_208.Geo(lat: 40.76, lon: -73.99, timestamp: 1587752000.0), __lldb_expr_208.Geo(lat: 40.76, lon: -73.99, timestamp: 1587762000.0), __lldb_expr_208.Geo(lat: 40.77, lon: -74.0, timestamp: 1587770000.0)]
1587749400.0
Address 0 : [35 E 38th St, 35 E 38th St, New York, NY 10016, United States @ <+40.75000000,-73.98000000> +/- 100.00m, region CLCircularRegion (identifier:'<+40.75005825,-73.97999890> radius 29.18', center:<+40.75005825,-73.97999890>, radius:29.18m)]
Address 2 : [341 W 45th St, 341 W 45th St, New York, NY 10036, United States @ <+40.76000000,-73.99000000> +/- 100.00m, region CLCircularRegion (identifier:'<+40.76011330,-73.99006390> radius 29.76', center:<+40.76011330,-73.99006390>, radius:29.76m)]
Address 1 : [35 E 38th St, 35 E 38th St, New York, NY 10016, United States @ <+40.75000000,-73.98000000> +/- 100.00m, region CLCircularRegion (identifier:'<+40.75005825,-73.97999890> radius 29.18', center:<+40.75005825,-73.97999890>, radius:29.18m)]
Address 4 : [Hudson River, Hudson River, New York, NY, United States @ <+40.77000000,-74.00000000> +/- 100.00m, region CLCircularRegion (identifier:'<+40.76197890,-74.00707395> radius 7528.16', center:<+40.76197890,-74.00707395>, radius:7528.16m)]
Address 3 : [341 W 45th St, 341 W 45th St, New York, NY 10036, United States @ <+40.76000000,-73.99000000> +/- 100.00m, region CLCircularRegion (identifier:'<+40.76011330,-73.99006390> radius 29.76', center:<+40.76011330,-73.99006390>, radius:29.76m)]
Finished the loop - find 5 addresses

AddressTSList: [{"address":"35 E 38th St, E 38th St, New York, United States","timestamp":1587749400},{"address":"35 E 38th St, E 38th St, New York, United States","timestamp":1587751200},{"address":"341 W 45th St, W 45th St, New York, United States","timestamp":1587752000},{"address":"341 W 45th St, W 45th St, New York, United States","timestamp":1587762000},{"address":"Hudson River, New York, United States","timestamp":1587770000}]

Final AddressPeriodList: [{"address":"35 E 38th St, E 38th St, New York, United States","period":"2020-04-24 13:30:00 ~ 2020-04-24 14:00:00"},{"address":"341 W 45th St, W 45th St, New York, United States","period":"2020-04-24 14:13:20 ~ 2020-04-24 17:00:00"},{"address":"Hudson River, New York, United States","period":"2020-04-24 19:13:20 ~ 2020-04-24 19:23:20"}]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment