@capgo/background-geolocation 7.0.9 → 7.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -67
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/BackgroundGeolocation.java +62 -0
- package/android/src/main/java/com/capgo/capacitor_background_geolocation/BackgroundGeolocationService.java +176 -0
- package/dist/docs.json +126 -30
- package/dist/esm/definitions.d.ts +74 -31
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +11 -1
- package/dist/esm/web.js +101 -0
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +101 -0
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +101 -0
- package/dist/plugin.js.map +1 -1
- package/ios/Plugin/Plugin.m +1 -0
- package/ios/Plugin/Plugin.swift +157 -0
- package/package.json +1 -1
package/ios/Plugin/Plugin.swift
CHANGED
|
@@ -2,6 +2,7 @@ import Capacitor
|
|
|
2
2
|
import Foundation
|
|
3
3
|
import UIKit
|
|
4
4
|
import CoreLocation
|
|
5
|
+
import AVFoundation
|
|
5
6
|
|
|
6
7
|
// Avoids a bewildering type warning.
|
|
7
8
|
let null = Optional<Double>.none as Any
|
|
@@ -40,6 +41,13 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate {
|
|
|
40
41
|
private var allowStale: Bool = false
|
|
41
42
|
private var isUpdatingLocation: Bool = false
|
|
42
43
|
private var activeCallbackId: String?
|
|
44
|
+
private var audioPlayer: AVAudioPlayer?
|
|
45
|
+
private var plannedRoute: [[Double]] = []
|
|
46
|
+
private var isOffRoute: Bool = true
|
|
47
|
+
private var distanceThreshold: Double = 50.0 // Default distance threshold in meters
|
|
48
|
+
|
|
49
|
+
// Earth radius in meters for distance calculations
|
|
50
|
+
private static let EARTH_RADIUS_M: Double = 6371000.0
|
|
43
51
|
|
|
44
52
|
@objc override public func load() {
|
|
45
53
|
UIDevice.current.isBatteryMonitoringEnabled = true
|
|
@@ -147,6 +155,54 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate {
|
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
158
|
+
@objc func setPlannedRoute(_ call: CAPPluginCall) {
|
|
159
|
+
DispatchQueue.global(qos: .background).async { [weak self] in
|
|
160
|
+
guard let self = self else { return }
|
|
161
|
+
|
|
162
|
+
// Get the sound file path
|
|
163
|
+
guard let soundFile = call.getString("soundFile") else {
|
|
164
|
+
call.reject("Sound file is required")
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Get the route array
|
|
169
|
+
let routeArray = call.getArray("route", JSObject.self) ?? []
|
|
170
|
+
var route: [[Double]] = []
|
|
171
|
+
|
|
172
|
+
for routePoint in routeArray {
|
|
173
|
+
if let pointArray = routePoint as? [Double], pointArray.count == 2 {
|
|
174
|
+
route.append(pointArray)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Get distance threshold
|
|
179
|
+
let distance = call.getDouble("distance") ?? 50.0
|
|
180
|
+
|
|
181
|
+
// Set up audio player
|
|
182
|
+
let assetPath = "public/" + soundFile
|
|
183
|
+
let assetPathSplit = assetPath.components(separatedBy: ".")
|
|
184
|
+
guard let url = Bundle.main.url(forResource: assetPathSplit[0], withExtension: assetPathSplit[1]) else {
|
|
185
|
+
call.reject("Sound file not found: \(assetPath)")
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
do {
|
|
190
|
+
self.audioPlayer?.stop()
|
|
191
|
+
self.audioPlayer = nil
|
|
192
|
+
self.audioPlayer = try AVAudioPlayer(contentsOf: url)
|
|
193
|
+
|
|
194
|
+
// Store route configuration
|
|
195
|
+
self.plannedRoute = route
|
|
196
|
+
self.distanceThreshold = distance
|
|
197
|
+
self.isOffRoute = true
|
|
198
|
+
|
|
199
|
+
call.resolve()
|
|
200
|
+
} catch {
|
|
201
|
+
call.reject("Could not load the sound file: \(error.localizedDescription)")
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
150
206
|
private func startUpdatingLocation() {
|
|
151
207
|
// Avoid unnecessary calls to startUpdatingLocation, which can
|
|
152
208
|
// result in extraneous invocations of didFailWithError.
|
|
@@ -171,6 +227,106 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate {
|
|
|
171
227
|
)
|
|
172
228
|
}
|
|
173
229
|
|
|
230
|
+
private func toRadians(_ degrees: Double) -> Double {
|
|
231
|
+
return degrees * Double.pi / 180.0
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private func haversine(_ point1: [Double], _ point2: [Double]) -> Double {
|
|
235
|
+
let lon1 = point1[0]
|
|
236
|
+
let lat1 = point1[1]
|
|
237
|
+
let lon2 = point2[0]
|
|
238
|
+
let lat2 = point2[1]
|
|
239
|
+
|
|
240
|
+
let dLat = toRadians(lat2 - lat1)
|
|
241
|
+
let dLon = toRadians(lon2 - lon1)
|
|
242
|
+
|
|
243
|
+
let aaa = sin(dLat / 2) * sin(dLat / 2) +
|
|
244
|
+
cos(toRadians(lat1)) * cos(toRadians(lat2)) *
|
|
245
|
+
sin(dLon / 2) * sin(dLon / 2)
|
|
246
|
+
|
|
247
|
+
let ccc = 2 * atan2(sqrt(aaa), sqrt(1 - aaa))
|
|
248
|
+
|
|
249
|
+
return BackgroundGeolocation.EARTH_RADIUS_M * ccc
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private func distancePointToLineSegment(_ point: [Double], _ lineStart: [Double], _ lineEnd: [Double]) -> Double {
|
|
253
|
+
// Calculate the distances between the three points using Haversine
|
|
254
|
+
let distAB = haversine(point, lineStart)
|
|
255
|
+
let distAC = haversine(point, lineEnd)
|
|
256
|
+
let distBC = haversine(lineStart, lineEnd)
|
|
257
|
+
|
|
258
|
+
// Handle the edge case where the line segment is a single point
|
|
259
|
+
if distBC == 0 {
|
|
260
|
+
return distAB
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check if the angles at the line segment's endpoints are obtuse.
|
|
264
|
+
// We use the Law of Cosines (c^2 = a^2 + b^2 - 2ab*cos(C))
|
|
265
|
+
// If cos(C) < 0, the angle is obtuse.
|
|
266
|
+
|
|
267
|
+
// Angle at B (lineStart)
|
|
268
|
+
let epsilon = Double.ulpOfOne
|
|
269
|
+
let cosB = (pow(distAB, 2) + pow(distBC, 2) - pow(distAC, 2)) / (2 * distAB * distBC + epsilon)
|
|
270
|
+
if cosB < 0 {
|
|
271
|
+
return distAB
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Angle at C (lineEnd)
|
|
275
|
+
let cosC = (pow(distAC, 2) + pow(distBC, 2) - pow(distAB, 2)) / (2 * distAC * distBC + epsilon)
|
|
276
|
+
if cosC < 0 {
|
|
277
|
+
return distAC
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// If both angles are acute, the closest point is on the line segment itself.
|
|
281
|
+
// We can calculate the distance (height of the triangle) using its area.
|
|
282
|
+
|
|
283
|
+
// 1. Calculate the semi-perimeter of the triangle ABC
|
|
284
|
+
let semi = (distAB + distAC + distBC) / 2
|
|
285
|
+
|
|
286
|
+
// 2. Calculate the area using Heron's formula
|
|
287
|
+
let area = sqrt(max(0, semi * (semi - distAB) * (semi - distAC) * (semi - distBC)))
|
|
288
|
+
|
|
289
|
+
// 3. The distance is the height of the triangle from point A to the base BC
|
|
290
|
+
// Area = 0.5 * base * height => height = 2 * Area / base
|
|
291
|
+
return (2 * area) / (distBC + epsilon)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private func distancePointToRoute(_ point: [Double]) -> Double {
|
|
295
|
+
// If the route has less than 2 points, we can't form a segment.
|
|
296
|
+
if plannedRoute.count < 2 {
|
|
297
|
+
if plannedRoute.count == 1 {
|
|
298
|
+
return haversine(point, plannedRoute[0])
|
|
299
|
+
}
|
|
300
|
+
return Double.infinity // No line segments to measure against
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
var minDistance = Double.infinity
|
|
304
|
+
|
|
305
|
+
for pointIndex in 0..<(plannedRoute.count - 1) {
|
|
306
|
+
let lineStart = plannedRoute[pointIndex]
|
|
307
|
+
let lineEnd = plannedRoute[pointIndex + 1]
|
|
308
|
+
let distance = distancePointToLineSegment(point, lineStart, lineEnd)
|
|
309
|
+
if distance < minDistance {
|
|
310
|
+
minDistance = distance
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return minDistance
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private func checkRouteDeviation(_ location: CLLocation) {
|
|
318
|
+
guard audioPlayer != nil && plannedRoute.count > 0 else { return }
|
|
319
|
+
|
|
320
|
+
let currentPoint = [location.coordinate.longitude, location.coordinate.latitude]
|
|
321
|
+
let offRoute = distancePointToRoute(currentPoint) > distanceThreshold
|
|
322
|
+
|
|
323
|
+
if offRoute && !isOffRoute {
|
|
324
|
+
audioPlayer?.play()
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
isOffRoute = offRoute
|
|
328
|
+
}
|
|
329
|
+
|
|
174
330
|
public func locationManager(
|
|
175
331
|
_ manager: CLLocationManager,
|
|
176
332
|
didFailWithError error: Error
|
|
@@ -207,6 +363,7 @@ public class BackgroundGeolocation: CAPPlugin, CLLocationManagerDelegate {
|
|
|
207
363
|
}
|
|
208
364
|
|
|
209
365
|
if isLocationValid(location) {
|
|
366
|
+
checkRouteDeviation(location)
|
|
210
367
|
return call.resolve(formatLocation(location))
|
|
211
368
|
}
|
|
212
369
|
}
|
package/package.json
CHANGED