Getting started - iOS

Notification Service with Location and API calls for iOS
  1. Woosmap Geofencing
    1. Use case where location matters:
  2. Overview
    1. Get user location
    2. Enrich Notification with user location and POIs (Points of Interest)
    3. Detect Visits (spending time at one place) of your users
    4. Detect Zone of Interest (cluster) of your users
  3. Pre-requisites
  4. Installation
  5. Get Keys
  6. Usage
  7. Simulate Notification
  8. GPX files
  9. Additional Documentation
  10. Contributing
  11. License

Woosmap Geofencing

Location intelligence is one of the next revolutions to improve and “smoothen” user experience on mobile. Mobile operating systems use and provide multiple location services that might be tricky to handle or tune to achieve advanced location based services on mobile. And users are more and more aware of the capabilities of their mobile devices. During the last two years, we analysed, exploited and followed the evolution of those location services, changes that occurred either on tech side or regulation side.

We are convinced that location is an effective way for App makers to propose tailor made and locally contextualised interactions with mobile users. But knowing the location of a user is not enough. Knowing from what a user is close to or what he is visiting is the important part. So we decided to share our findings and tricks for location collection on mobile to help you focus on this real value of location.

This repository is designed to share samples of codes and a SDK on iOS to take the best of location in your mobile apps. We had 3 main focus when developing and sharing this code: take full advantage of location capabilities of mobile devices, doing so in a battery friendly way and be fair with user privacy (see Enabling Location).

Woosmap Geofencing SDK and sample app should help you build Rich Push Notifications (highlighted with a Location context), analyse your mobile users surroundings (search for proximity to your assets, competitors, etc) and much more on iOS to go a step further on Location Intelligence.

Use case where location matters:

As a banking company, you need to enrich the expense list with local information (logo, shop info, etc)? Use this repo to learn how to leverage locally enriched notification to retrieve local information where your users spend their money.

As a retailer company, you need to be informed when a user is close to one of your stores (or competitors’)? Find in this repo how to collect in background location of your users and build your own geofencing analysis.

As a retailer, insurance, banking or travel company, you need to add local context to your user profiles in your CRM? Build your own process of background location collection in your mobile app and analyze geographic behaviors of your mobile users.

As a retailer, insurance, banking or travel company, you want to be informed when a user is visiting specific POIs you decided to monitor (your own stores, your competitors, specific locations)? Use our SDK/code samples to not just collect location but directly obtain “visit triggers” and build advanced scenarios (e.g. a Bank being able to propose specific loans or services when users visits Real Estate agencies, Car Dealers, etc - an Insurance company proposing travel insurance when users visit airports, car insurance when users visit car dealers, etc)

Overview

Get user location

Collect in background user’s locations and host them in a local database. Call the Woosmap Search API to retrieve closest stores to each location to locally contextualized users journeys.

WoosmapGeofencing

Enrich Notification with user location and POIs (Points of Interest)

Get the location of a user on notification reception, to complete the payload with local information from third parties APIs.
In this sample, fetched location is then used to perform a request to the Woosmap Search API to get the closest POIs (Points of Interest) to the location of the user. In addition, a call to Google Static Map is performed to enrich the notification with a map displaying the user’s location and the closest POIs.

Notification Location

Detect Visits (spending time at one place) of your users

Get the location and the time spent when a user is visiting places. Once again use the Woosmap Search API if needed to detect if your users visit your stores, your competitors or POI you may want to monitor.

Visit

Detect Zone of Interest (cluster) of your users

Identify areas of interest for your users (location where they spend time, once or recurrently).

Visit Visit

Pre-requisites

Installation

Get Keys

WoosmapGeofencing with POI from Search API

Search API

Google map Static

Google map Static

Usage

Be sure your Private Key for the Woosmap Search API is set every time your app is launched (in Foreground AND Background). This should be done as early as possible in your didFinishLaunchingWithOptions App Delegate. Depending on your integration, you should call startMonitoringInBackground too. This method must also be called everytime your app is launched. Set the locationServiceDelegate, searchAPIDataDelegate and visitDelegate to retrieve data of location, POI when the data is ready and visit data if the the visit is enabled.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Set private key Search API
        WoosmapGeofencing.shared.setWoosmapAPIKey(key: "YOUR_WOOSMAP_KEY")
        
        // Set your filter on position location and search
        WoosmapGeofencing.shared.setCurrentPositionFilter(distance: 10.0, time: 10)
        WoosmapGeofencing.shared.setSearchAPIFilter(distance: 10.0, time: 10)
        
        // Initialize the framework
        WoosmapGeofencing.shared.initServices()
        
        // Set delegate of protocol Location and POI
        WoosmapGeofencing.shared.getLocationService().locationServiceDelegate = DataLocation()
        WoosmapGeofencing.shared.getLocationService().searchAPIDataDelegate = DataPOI()
        
        // Enable Visit and set delegate of protocol Visit
        WoosmapGeofencing.shared.setVisitEnable(enable: true)
        WoosmapGeofencing.shared.getLocationService().visitDelegate = DataVisit()
 
         // Check if the authorization Status of location Manager
         if (CLLocationManager.authorizationStatus() != .notDetermined) {
             WoosmapGeofencing.shared.startMonitoringInBackground()
         }
    return true
}

In order to avoid loosing data, you also need to call startMonitoringInBackground in the proper AppDelegate method :

func applicationDidEnterBackground(_ application: UIApplication) {
    if (CLLocationManager.authorizationStatus() != .notDetermined) {
        WoosmapGeofencing.shared.startMonitoringInBackground()
    }
}

To keep the SDK up to date with user’s data, you need to call didBecomeActive in the proper AppDelegate method too.

func applicationDidBecomeActive(_ application: UIApplication) {
    WoosmapGeofencing.shared.didBecomeActive()
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

In your class delegate, retrieve location data and POI date:

func tracingLocation(locations: [CLLocation], locationId: UUID) {
        let location = locations.last!
  
        let locationToSave = LocationModel(locationId: locationId, latitude: location.coordinate.latitude, longitude: location.coordinate.longitude, dateCaptured: Date(), descriptionToSave: "description")
        print("location to save = " + locationToSave.dateCaptured.stringFromDate())
        createLocation(location: locationToSave)
        self.lastLocation = location
    }
    
    func tracingLocationDidFailWithError(error: Error) {
        NSLog("\(error)")
    }

func searchAPIResponseData(searchAPIData: SearchAPIData, locationId: UUID) {
    for feature in (searchAPIData.features)! {        
    	let city = feature.properties!.address!.city!
        let zipCode = feature.properties!.address!.zipcode!
        let distance = feature.properties!.distance!
        let latitude = (feature.geometry?.coordinates![1])!
        let longitude = (feature.geometry?.coordinates![0])!
        let dateCaptured = Date()
        let POIToSave = POIModel(locationId: locationId,city: city,zipCode: zipCode,distance: distance,latitude: latitude, longitude: longitude,dateCaptured: dateCaptured)
        createPOI(POImodel: POIToSave)
    }
}
func serachAPIError(error: String) {
       // Catch Error
       NSLog("\(error)")
}

For the visits, in the app delegate, you can retrieve the visit like this:

func processVisit(visit: CLVisit) {
    let calendar = Calendar.current
    let departureDate = calendar.component(.year, from: visit.departureDate) != 4001 ? visit.departureDate : nil
    let arrivalDate = calendar.component(.year, from: visit.arrivalDate) != 4001 ? visit.arrivalDate : nil
    let visitToSave = VisitModel(arrivalDate: arrivalDate, departureDate: departureDate, latitude: visit.coordinate.latitude, longitude: visit.coordinate.longitude, dateCaptured:Date() , accuracy: visit.horizontalAccuracy)
    
    createVisit(visit: visitToSave)
}

Retrieve Zone of Interest ZOIs are built from visits, grouped by proximity. We use the Fast Incremental Gaussian Mixture Model of classification Algorithm FIGMM to build and update our ZOI according to visits recurrency along time.

Create the ZOI when a visit is created :

func createVisit(visit: VisitModel) {
	...
    DataZOI().createZOIFromVisit(visit: newVisit)
}

To create ZOI, you must retrieve all the ZOI in database, calculate the new ZOIs, erase the old ZOIs in database, save the new ZOIs:

func createZOIFromVisit(visit : Visit) {
   	//Retrieve the zois in database
    let sMercator = SphericalMercator()
    var zoisFromDB: [Dictionary<String, Any>] = []
    for zoiFromDB in readZOIs(){
        var zoiToAdd = Dictionary<String, Any>()
        zoiToAdd["prior_probability"] = zoiFromDB.prior_probability
        zoiToAdd["mean"] = [zoiFromDB.latMean, zoiFromDB.lngMean]
        zoiToAdd["age"] = zoiFromDB.age
        zoiToAdd["accumulator"] = zoiFromDB.accumulator
        zoiToAdd["idVisits"] = zoiFromDB.idVisits
        zoiToAdd["startTime"] = zoiFromDB.startTime
        zoiToAdd["endTime"] = zoiFromDB.endTime
        zoiToAdd["covariance_det"] = zoiFromDB.covariance_det
        zoiToAdd["x00Covariance_matrix_inverse"] = zoiFromDB.x00Covariance_matrix_inverse
        zoiToAdd["x01Covariance_matrix_inverse"] = zoiFromDB.x01Covariance_matrix_inverse
        zoiToAdd["x10Covariance_matrix_inverse"] = zoiFromDB.x10Covariance_matrix_inverse
        zoiToAdd["x11Covariance_matrix_inverse"] = zoiFromDB.x11Covariance_matrix_inverse
        zoisFromDB.append(zoiToAdd)
        
    }
    
    // Set the data zois for calculation
    setListZOIsFromDB(zoiFromDB: zoisFromDB)

	// Calculation
    let list_zoi = figmmForVisit(newVisitPoint: MyPoint(x: sMercator.lon2x(aLong: visit.longitude), y: sMercator.lat2y(aLat:visit.latitude),accuracy: visit.accuracy, id:visit.visitId!, startTime: visit.arrivalDate!, endTime: visit.departureDate!))
    
    // Erase the old data
    eraseZOIs()
    
    // Store zoi in database
    for zoi in list_zoi{
        createZOIFrom(zoi: zoi)
    }
    
}

When you store a ZOI in database, you must define the duration the ZOI, the departure and arrival date time like that:

func createZOIFrom(zoi: Dictionary<String, Any>) {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    let context = appDelegate.persistentContainer.viewContext
    let entity = NSEntityDescription.entity(forEntityName: "ZOI", in: context)!
    let newZOi = ZOI(entity: entity, insertInto: context)
    newZOi.setValue(UUID(), forKey: "zoiId")
    newZOi.setValue(zoi["idVisits"], forKey: "idVisits")
    
    var visitArrivalDate = [Date]()
    var visitDepartureDate = [Date]()
    var duration = 0
    for id in zoi["idVisits"] as! [UUID] {
        let visit = DataVisit().getVisitFromUUID(id: id)
        visitArrivalDate.append(visit!.arrivalDate!)
        visitDepartureDate.append(visit!.departureDate!)
        duration += visit!.departureDate!.seconds(from: visit!.arrivalDate!)
    }
    let startTime = visitArrivalDate.reduce(visitArrivalDate[0], { $0.timeIntervalSince1970 < $1.timeIntervalSince1970 ? $0 : $1 } )
    let endTime = visitDepartureDate.reduce(visitDepartureDate[0], { $0.timeIntervalSince1970 > $1.timeIntervalSince1970 ? $0 : $1 } )
    
    newZOi.setValue(startTime , forKey: "startTime")
    newZOi.setValue(endTime, forKey: "endTime")
    newZOi.setValue(duration, forKey: "duration")
    newZOi.setValue((zoi["mean"] as! Array<Any>)[0] as! Double, forKey: "latMean")
    newZOi.setValue((zoi["mean"] as! Array<Any>)[1] as! Double, forKey: "lngMean")
    newZOi.setValue(zoi["age"] , forKey: "age")
    newZOi.setValue(zoi["accumulator"] , forKey: "accumulator")
    newZOi.setValue(zoi["covariance_det"] , forKey: "covariance_det")
    newZOi.setValue(zoi["prior_probability"] , forKey: "prior_probability")
    newZOi.setValue(zoi["x00Covariance_matrix_inverse"], forKey: "x00Covariance_matrix_inverse")
    newZOi.setValue(zoi["x01Covariance_matrix_inverse"], forKey: "x01Covariance_matrix_inverse")
    newZOi.setValue(zoi["x10Covariance_matrix_inverse"], forKey: "x10Covariance_matrix_inverse")
    newZOi.setValue(zoi["x11Covariance_matrix_inverse"], forKey: "x11Covariance_matrix_inverse")
    newZOi.setValue(zoi["WktPolygon"], forKey: "wktPolygon")
    
    do {
        try context.save()
    }
    catch let error as NSError {
        print("Could not insert. \(error), \(error.userInfo)")
    }

}

Each ZOI includes the following informations:

public var zoiId: UUID?
public var idVisits: [UUID]?
public var lngMean: Double
public var latMean: Double
public var age: Double
public var accumulator: Double
public var covariance_det: Double
public var prior_probability: Double
public var x00Covariance_matrix_inverse: Double
public var x01Covariance_matrix_inverse: Double
public var x10Covariance_matrix_inverse: Double
public var x11Covariance_matrix_inverse: Double
public var startTime: Date?
public var endTime: Date?
public var duration: Int64
public var wktPolygon: String?

Simulate Notification

GPX files

To test geolocation in an iOS app, you can mock a route to simulate locations.
To create a gpx files, the following tool converts a Google Maps link (also works with Google Maps Directions) to a .gpx file: https://mapstogpx.com/mobiledev.php To emulate, follow instructions here: http://www.madebyuppercut.com/testing-geolocation-ios-app/

Additional Documentation

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Thank you for your suggestions!

License

Woosmap Geofencing is released under the MIT License. See LICENSE file for details.