iOS - swift 3 - heatmap - ios

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)
}

Related

Coordinates are nil when selecting annotation on map Swift

I'm changing one part of my app from displaying some shops in a UITableview to display them on a MKMapView and they display fine but I'm getting nil coordinate values when I select the annotations on the map. In didAdd I print them and they are fine, and in fact the annotation is displayed on map.
It has been a while since I last used MapKit and I can't spot if the problem is in the custom annotation class or somewhere else. Can you spot where I'm misusing it?
As always many thanks for you time and help.
This is the annotation class:
class ShopAnnotation: NSObject , MKAnnotation {
var title: String?
var coordinate:CLLocationCoordinate2D
var image: UIImage?
var shopName: String?
var shopLogoUrl: String?
init(title: String, iconImage: UIImage, coordinate:CLLocationCoordinate2D, shopName: String, shopLogoUrl: String) {
self.coordinate = coordinate
self.title = title
self.shopName = shopName
self.shopLogoUrl = shopLogoUrl
self.image = iconImage
}
}
than I have a an array that I take values from :
var availableShopsArray:[(name:String, logoUrl:String, homeLat: String, homeLong: String)] = []
I than loop through it and place an annotation on the map:
func displayShops() {
mapView.removeAnnotations(mapView.annotations)
for shop in availableShopsArray {
let coordinates: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: Double(shop.homeLat)!, longitude: Double(shop.homeLong)!)
let title = "Route Mid"
let image = UIImage(named: title)!
let shopAnn: ShopAnnotation = ShopAnnotation(title: title, iconImage: image, coordinate: coordinates, shopName: shop.name, shopLogoUrl: shop.logoUrl)
mapView.addAnnotation(shopAnn)
}
// self.availableShopsTableview.reloadData()
}
when I select one I should take the values from it, but coordinate is nil :
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let selectedShop = view.annotation as? ShopAnnotation else {return}
self.shopLogoUrl = selectedShop.shopLogoUrl!
self.selectedShopCoordinates.latitude = selectedShop.coordinate.latitude
self.selectedShopCoordinates.longitude = selectedShop.coordinate.longitude
self.selectedShopName = selectedShop.shopName
// calculateBookingType()
self.performSegue(withIdentifier: "bookingToCartSegue", sender: self)
}
and this is viewForAnnotation :
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // rajish version
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "")
if annotation is MKUserLocation{
return nil
} else {
print("Annotation.coordinates are: \(String(describing: annotation.coordinate))")
// print("annotation title is: \(String(describing: annotation.title!))")
annotationView.image = UIImage(named:(annotationView.annotation?.title)! ?? "")
annotationView.canShowCallout = true
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27) // alert annotation's icons size
annotationView.transform = transform
// makin allerts draggable
annotationView.isDraggable = false
return annotationView
}
}
Weirdly enough I was assigning latitude and longitude separately to selectedShopCoordinates from selectedShop.coordinateand on that it would find nil.
I now assign them as coordinates as selectedShopCoordinates = selectedShop.coordinate and is not crashing anymore.
Why would it find nil on single coordinates degrees is still a mystery.
Any idea on why would be very much appreciated.
Hope this will help others struggling with it.
Cheers

Mapbox Navigation in iOS with in my mapView controller

I want to integrate Mapbox navigation in iOS, I can easily get the direction/route between two coordinate also to get the navigation path from mapbox we can use below code
let options = NavigationOptions(styles: nil)
let viewController = NavigationViewController(for: self.directionsRoute!)
viewController.delegate=self
self.present(viewController, animated: true, completion: nil)
But the problem is I want to display the navigation in my mapview which is a part of another view controller, I can do that by getting a direction/route and instruction but I can't find any method which will be called every second so that I can update route instruction, as well as route, in case of user change the path.
Let me know if I am missing anything or any changes needed.
-Thanks in advance
here is my approach:
first i did get only directions instructions from the MapBox api taking advantage of it's free API calls quota and draw the instructions on GMSMapView or MapKit taking advantage of their good performance and memory management.
podfile
pod 'MapboxDirections.swift'
import MapboxDirections
this is done through the below code
have the property for MapBox directions
#IBOutlet weak var googleMapView: GMSMapView!
let locationManager = CLLocationManager()
let mapBoxirections = Directions(accessToken: osmToken)
var path: GMSMutablePath?
then do the actual api call
private func drawRouteBetween(source: StopModel, destination: StopModel) {
guard let name = source.name, let lat = source.latitude, let lng = source.longitude else { return }
guard let nameDest = destination.name, let latDest = destination.latitude, let lngDest = destination.longitude else { return }
let waypoints = [
Waypoint(coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lng), name: name),
Waypoint(coordinate: CLLocationCoordinate2D(latitude: latDest, longitude: lngDest), name: nameDest),
]
let options = RouteOptions(waypoints: waypoints, profileIdentifier: .automobile)
options.includesSteps = true
options.distanceMeasurementSystem = .metric
mapBoxirections.calculate(options) { (waypoints, routes, error) in
guard error == nil else {
print("Error calculating directions: \(error!)")
return
}
if let route = routes?.first, let leg = route.legs.first {
for step in leg.steps {
if let coordinates = step.coordinates {
for (index, point) in coordinates.enumerated() {
let source = point
if index <= coordinates.count - 2 {
let destination = coordinates[index + 1]
self.drawPolyLine(source: source, destination: destination)
}
}
}
}
}
}
}
note that StopModel is my custom made CLLocation so feel free to replace it with your own as long it has the latitude and longitude
create the method that draws Polyline on your CLLocationManagerDelegate as below
private func drawPolyLine(source: CLLocationCoordinate2D, destination: CLLocationCoordinate2D){
path?.add(source)
path?.add(destination)
let polyLine = GMSPolyline(path: path)
polyLine.strokeWidth = 4 // width of your choice
polyLine.strokeColor = .red // color of your choice
polyLine.map = googleMapView
}
then take a look at the MapBoxDirections.Route model and explore it's properties you will find very useful info inside it
and then take advantage of the callback function from the GMS Delegate that notifies you with the location update instead having a timer and calling it every second this is more efficient way
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
/* do your business here */
}
do not forget to have the delegate of the location manager to self or the class of your choice
Maybe this helps a bit: you can easily add observer for route progress changes:
NotificationCenter.default.addObserver(self,
selector: #selector(progressDidChange(notification:)),
name: .routeControllerProgressDidChange,
object: navigationService.router)
You need a navigation service with your route by creating it like
let navigationService = MapboxNavigationService(route: route)
The function progressDidChange can do something like:
#objc func progressDidChange(notification: NSNotification) {
guard let routeProgress = notification.userInfo?[RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress,
let location = notification.userInfo?[RouteControllerNotificationUserInfoKey.locationKey] as? CLLocation else {
return
}
// you have all information you probably need in routeProgress, f.E.
let secondsRemaining = routeProgress.currentLegProgress.currentStepProgress.durationRemaining
...
}

MK Directions Request - direction line is not shown

I have successfully implemented location of a point of interest and my location. Both is shown. Now, I would like to get calculated the route between two points and a blue line should be shown. Unfortunately, when I am clicking on the button, no line is being shown.
I really appreciate help/hints. Thanks so much.
import UIKit
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate
{
//outlet variable is used for establishing a connection with the
// map view in the storyboard
#IBOutlet var mapView: MKMapView!
var spot = Spot()
let locationManager = CLLocationManager()
var currentPlacemark:CLPlacemark?// it is used to save the selected spot
override func viewDidLoad()
{
super.viewDidLoad()
//request for a user's authorization for lacation services
locationManager.requestWhenInUseAuthorization()
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.authorizedWhenInUse
{
mapView.showsUserLocation = true
}
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(spot.location,
completionHandler:
{ placemarks, error in
if let error = error {
print(error)
return
}
if let placemarks = placemarks{
//get the first placemark
let placemark = placemarks[0]
// value of current Placemark
self.currentPlacemark = placemark
// add annotation
let annotation = MKPointAnnotation()
annotation.title = self.spot.name
annotation.subtitle = self.spot.type
if let location = placemark.location{
annotation.coordinate = location.coordinate
//display the annotation
self.mapView.showAnnotations([annotation],animated:true)
self.mapView.selectAnnotation(annotation, animated: true)
}
}
})
mapView.showsCompass = true
mapView.showsTraffic = true
mapView.showsScale = true
// we want to show the users location
mapView.showsUserLocation = true
}
#IBAction func showDirection(sender:AnyObject)
{
// we make sure if current placemark contains a value using a guard statement. Otherwise just
// skip everything
guard let currentPlacemark = currentPlacemark else
{
return
}
// creating an instance of MKDirectionsRequest to request directions
let directionRequest = MKDirectionsRequest()
// set the source(where the user currently is) and destination of the route
directionRequest.source = MKMapItem.forCurrentLocation()// retrieving the current location
let destinationPlacemark = MKPlacemark(placemark:currentPlacemark)
directionRequest.destination = MKMapItem(placemark: destinationPlacemark)
directionRequest.transportType = MKDirectionsTransportType.automobile// later change for transit
// calculate the direction
let directions = MKDirections(request: directionRequest)
// this method initiates an asynchronous request for directions and calls
// your completion handler when the request is conpleted. The MKDirections object
//passes my request to the Apple servers ans asks for route-based directions data
directions.calculate { (routeRepsonse, routeError) -> Void in
guard let routeResponse = routeRepsonse else
{
if let routeError = routeError
{
print("Error:\(routeError)")
}
return
}
let route = routeRepsonse?.routes[0]// provides a container for saving the route information so that the routes are saved in the routes property
// The detailed route geometry is e.g. route.polyline is represented by an MKPolyline object
// the add level method is used to add an MKPolyline object to the existing map view
self.mapView.add((route?.polyline)!,level: MKOverlayLevel.aboveRoads)
}
}
// implementing a mapView method which draws the route
func mapView(_mapView:MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer
{
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.strokeColor = UIColor.blue
renderer.lineWidth = 3.0
return renderer
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources hat can be recreated.
}
}
Maybe delegate method name is wrong. Rename
func mapView(_mapView:MKMapView,rendererFor overlay: MKOverlay) -> MKOverlayRenderer
with
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer

Passing an object to custom view in Google maps SDK

I have custom view (a nib with its class) that I show when user taps on map marker. I also have an array of objects from where I'd like to show data of selected object when user taps on one of the markers. I create markers from the same array in a separate method. How to I get my element in array (to show additional data) when user taps the marker ? Or is there another way to get the object from array based on pressed marker. Apart from regexing based on lon and lat etc ?
I solved this by adding my model to
marker.userData
example:
func addMarkers(){
for place in places{
let marker = GMSMarker()
let placeLat = place.latitude
let placeLon = place.longitude
marker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(placeLat), longitude: CLLocationDegrees(placeLon))
marker.appearAnimation = .pop
marker.map = mpView
marker.userData = place
}
}
Then later I access it in:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let title = (marker.userData as! placeModel).name
let add = (marker.userData as! placeModel).address
let id = (marker.userData as! placeModel).id
let image = (marker.userData as! placeModel).image
let imageAddressInDocuments = ("\(getDocumentsDirectory())\(image)")
print("image address in documents %#", imageAddressInDocuments )
var infoWindow = Bundle.main.loadNibNamed("custView", owner: self, options: nil)?.first as! custView
infoWindow.titleLabel.text = title
infoWindow.addressLabel.text = add
infoWindow.restaurantImage.image = image != "" ? UIImage(contentsOfFile: imageAddressInDocuments) : UIImage(named: "addImage")
return infoWindow
}
GMSMapViewDelegate has the delegate function which lets you to return
your customView as info window.
optional public func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView?
You have to subclass GSMarker which takes your model in init.
class MapItem:GMSMarker {
var model: Model! //your data model
init(data: Model) {
super.init()
// pass cordinate
self.position = CLLocationCoordinate2D(latitude: data.latitude!, longitude: data.longitude!)
// pass Model
self.model = data
}
}
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
// Wrap GSMarker to your custom marker which is MapItem here
let customMarker = marker as! MapItem
// pass your model to CustomView(marker info window) in init
let customView = CustomView(markerModel: customMarker.model)
return customView
}

swift: what is the correct way of working with current location

I am quite new to swift and IOS development.
I would like to ask experienced members what is the correct way of creating mapvie and tableview in one viewcontroller and populating them.
The logic of application is the following:
get current location of user
read plist file with POI coordinates
for each POI run a function wich calculates distance between user and point
populate table with data from plist file plus newly calculated data.
Both mapview and tableview are in the same viewcontroller.
in viewDidLoad I am getting users location.
in viewwillappear I am running functions to read plist file and calculate distances between POIs and user.
Everything is working but it is not stable ... sometimes it might show user's location but table will be empty. So I doubt that I am doing everything correctly. Also probably it is not correct to put both map and table inside one class?
Update
Here is the code:
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var theMapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
var locationManager: CLLocationManager!
var branches = [Branch]()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
tableView.dataSource = self
tableView.delegate = self
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
//mapview setup to show user location
theMapView.delegate = self
theMapView.showsUserLocation = true
theMapView.mapType = MKMapType(rawValue: 0)!
theMapView.userTrackingMode = MKUserTrackingMode(rawValue: 2)!
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
readFromPlist()
}
//MARK: UITableView methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return branches.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MapCustomCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! MapCustomCell
let brnch = branches[indexPath.row]
cell.mapSetupCell(brnch.cityName, AddressLabel: brnch.address, DistanceLabel: brnch.distance)
return cell
}
//MARK: FUNC TO CALCULATE DISTANCE
func calculateDistance (lat1 lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> String {
return "100km"
}
func locationManager (manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let myCoordinates = locations.last
let myLat = myCoordinates!.coordinate.latitude
let myLong = myCoordinates!.coordinate.longitude
let myCoordinates2D = CLLocationCoordinate2DMake(myLat, myLong)
let myLatDelta = 0.10
let myLongDelta = 0.10
let mySpan = MKCoordinateSpanMake(myLatDelta, myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoordinates2D, span: mySpan)
theMapView.setRegion(myRegion, animated: true)
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoordinates2D
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error:" + error.localizedDescription)
}
func readFromPlist() {
//read plist file to extract banks coordinates
let path = NSBundle.mainBundle().pathForResource("poi", ofType: "plist")
let POIarrays = NSArray(contentsOfFile: path!)
for arr in POIarrays! {
var ctName : String!
var brnchAddress : String!
var wrkngHours : String!
var lat : Double!
var long : Double!
ctName = arr.objectForKey("cityName")! as! String
brnchAddress = arr.objectForKey("address")! as! String
wrkngHours = arr.objectForKey("workingHours")! as! String
lat = Double(arr.objectForKey("latitude")! as! String)
long = Double(arr.objectForKey("longitude")! as! String)
let latitude: CLLocationDegrees = lat
let longitude : CLLocationDegrees = long
let bankLocation : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let annotation = MKPointAnnotation()
annotation.coordinate = bankLocation
annotation.title = bnkName
annotation.subtitle = brnchAddress
self.theMapView.addAnnotation(annotation)
let myLatitude = self.locationManager.location?.coordinate.latitude
let myLongitude = self.locationManager.location?.coordinate.longitude
if myLatitude != nil {
let dist = calculateDistance(lat1: latitude, lon1: longitude, lat2: myLatitude!, lon2: myLongitude!)
let b = Branch(cityName: ctName!, address: brnchAddress!, distance: dist)
branches.append(b)
}
}
}
}
Sometimes I got an error "error:The operation couldn’t be completed. (kCLErrorDomain error 0.)" and current location doesn't appear on my map.

Resources