@capgo/background-geolocation 7.0.14 → 7.0.18

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.
@@ -22,358 +22,296 @@ import com.getcapacitor.Logger;
22
22
  // added, and demoted when the last background watcher is removed.
23
23
  public class BackgroundGeolocationService extends Service {
24
24
 
25
- static final String ACTION_BROADCAST =
26
- (BackgroundGeolocationService.class.getPackage().getName() + ".broadcast");
27
- private final IBinder binder = new LocalBinder();
28
-
29
- private static final double EARTH_RADIUS_M = 6371000;
30
-
31
- // Must be unique for this application.
32
- private static final int NOTIFICATION_ID = 28351;
33
-
34
- private String callbackId;
35
-
36
- private LocationManager client;
37
- private LocationListener locationCallback;
38
- private MediaPlayer mediaPlayer;
39
- private double[][] route;
40
- private double distanceThreshold;
41
- private boolean isOffRoute;
42
-
43
- @Override
44
- public IBinder onBind(Intent intent) {
45
- return binder;
46
- }
47
-
48
- // Some devices allow a foreground service to outlive the application's main
49
- // activity, leading to nasty crashes as reported in issue #59. If we learn
50
- // that the application has been killed, all watchers are stopped and the
51
- // service is terminated immediately.
52
- @Override
53
- public boolean onUnbind(Intent intent) {
54
- if (client != null && locationCallback != null) {
55
- client.removeUpdates(locationCallback);
56
- }
57
- releaseMediaPlayer();
58
- stopSelf();
59
- return false;
60
- }
61
-
62
- @Override
63
- public void onDestroy() {
64
- if (client != null && locationCallback != null) {
65
- client.removeUpdates(locationCallback);
66
- }
67
- super.onDestroy();
68
- releaseMediaPlayer();
69
- }
25
+ static final String ACTION_BROADCAST = (BackgroundGeolocationService.class.getPackage().getName() + ".broadcast");
26
+ private final IBinder binder = new LocalBinder();
70
27
 
71
- private void releaseMediaPlayer() {
72
- if (mediaPlayer == null) {
73
- return;
74
- }
75
- try {
76
- if (mediaPlayer.isPlaying()) {
77
- mediaPlayer.stop();
78
- }
79
- mediaPlayer.release();
80
- } catch (Exception e) {
81
- Logger.error("Error releasing MediaPlayer", e);
82
- }
83
- mediaPlayer = null;
84
- }
85
-
86
- // Handles requests from the activity.
87
- public class LocalBinder extends Binder {
88
-
89
- void start(
90
- final String id,
91
- final String notificationTitle,
92
- final String notificationMessage,
93
- float distanceFilter
94
- ) {
95
- releaseMediaPlayer();
96
- client = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
97
- callbackId = id;
98
-
99
- locationCallback = location -> {
100
- if (mediaPlayer != null) {
101
- double[] point = { location.getLongitude(), location.getLatitude() };
102
- var offRoute = distancePointToRoute(point) > distanceThreshold;
103
- if (offRoute == true && isOffRoute == false) {
104
- mediaPlayer.start();
105
- }
106
- isOffRoute = offRoute;
107
- }
108
- Intent intent = new Intent(ACTION_BROADCAST);
109
- intent.putExtra("location", location);
110
- intent.putExtra("id", callbackId);
111
- LocalBroadcastManager.getInstance(
112
- getApplicationContext()
113
- ).sendBroadcast(intent);
114
- };
115
-
116
- try {
117
- client.requestLocationUpdates(
118
- LocationManager.GPS_PROVIDER,
119
- 1000,
120
- distanceFilter,
121
- locationCallback
122
- );
123
- } catch (SecurityException ignore) {
124
- // According to Android Studio, this method can throw a Security Exception if
125
- // permissions are not yet granted. Rather than check the permissions, which is fiddly,
126
- // we simply ignore the exception.
127
- }
128
-
129
- // Promote the service to the foreground if necessary.
130
- // Ideally we would only call 'startForeground' if the service is not already
131
- // foregrounded. Unfortunately, 'getForegroundServiceType' was only introduced
132
- // in API level 29 and seems to behave weirdly, as reported in #120. However,
133
- // it appears that 'startForeground' is idempotent, so we just call it repeatedly
134
- // each time a background watcher is added.
135
- try {
136
- // This method has been known to fail due to weird
137
- // permission bugs, so we prevent any exceptions from
138
- // crashing the app. See issue #86.
139
- startForeground(
140
- NOTIFICATION_ID,
141
- createBackgroundNotification(notificationTitle, notificationMessage)
142
- );
143
- } catch (Exception exception) {
144
- Logger.error("Failed to foreground service", exception);
145
- }
146
- }
28
+ private static final double EARTH_RADIUS_M = 6371000;
147
29
 
148
- String stop() {
149
- client.removeUpdates(locationCallback);
150
- stopForeground(true);
151
- stopSelf();
152
- releaseMediaPlayer();
153
- return callbackId;
30
+ // Must be unique for this application.
31
+ private static final int NOTIFICATION_ID = 28351;
32
+
33
+ private String callbackId;
34
+
35
+ private LocationManager client;
36
+ private LocationListener locationCallback;
37
+ private MediaPlayer mediaPlayer;
38
+ private double[][] route;
39
+ private double distanceThreshold;
40
+ private boolean isOffRoute;
41
+
42
+ @Override
43
+ public IBinder onBind(Intent intent) {
44
+ return binder;
154
45
  }
155
46
 
156
- void setPlannedRoute(
157
- String filePath,
158
- double[][] routeCoordinates,
159
- float distance
160
- ) {
161
- route = routeCoordinates;
162
- distanceThreshold = distance;
163
- isOffRoute = true;
164
- try {
165
- if (mediaPlayer != null) {
166
- return;
47
+ // Some devices allow a foreground service to outlive the application's main
48
+ // activity, leading to nasty crashes as reported in issue #59. If we learn
49
+ // that the application has been killed, all watchers are stopped and the
50
+ // service is terminated immediately.
51
+ @Override
52
+ public boolean onUnbind(Intent intent) {
53
+ if (client != null && locationCallback != null) {
54
+ client.removeUpdates(locationCallback);
167
55
  }
168
- mediaPlayer = new MediaPlayer();
169
- AssetManager am = getApplicationContext().getResources().getAssets();
170
- AssetFileDescriptor assetFileDescriptor = am.openFd(
171
- "public/" + filePath
172
- );
173
-
174
- mediaPlayer.setDataSource(
175
- assetFileDescriptor.getFileDescriptor(),
176
- assetFileDescriptor.getStartOffset(),
177
- assetFileDescriptor.getLength()
178
- );
179
- mediaPlayer.setLooping(false);
180
-
181
- mediaPlayer.setOnErrorListener((mp, what, extra) -> {
182
- Logger.error("MediaPlayer error: what=" + what + ", extra=" + extra);
183
- releaseMediaPlayer();
184
- return true; // Indicate we handled the error
185
- });
186
-
187
- mediaPlayer.prepareAsync();
188
- } catch (Exception e) {
189
- Logger.error("PlaySound: Unexpected error", e);
190
56
  releaseMediaPlayer();
191
- }
192
- }
193
- }
194
-
195
- private Notification createBackgroundNotification(
196
- String backgroundTitle,
197
- String backgroundMessage
198
- ) {
199
- Notification.Builder builder = new Notification.Builder(
200
- getApplicationContext()
201
- )
202
- .setContentTitle(backgroundTitle)
203
- .setContentText(backgroundMessage)
204
- .setOngoing(true)
205
- .setPriority(Notification.PRIORITY_HIGH)
206
- .setWhen(System.currentTimeMillis());
207
-
208
- try {
209
- String name = getAppString(
210
- "capacitor_background_geolocation_notification_icon",
211
- "mipmap/ic_launcher",
212
- getApplicationContext()
213
- );
214
- String[] parts = name.split("/");
215
- // It is actually necessary to set a valid icon for the notification to behave
216
- // correctly when tapped. If there is no icon specified, tapping it will open the
217
- // app's settings, rather than bringing the application to the foreground.
218
- builder.setSmallIcon(
219
- getAppResourceIdentifier(parts[1], parts[0], getApplicationContext())
220
- );
221
- } catch (Exception e) {
222
- Logger.error("Could not set notification icon", e);
57
+ stopSelf();
58
+ return false;
223
59
  }
224
60
 
225
- try {
226
- String color = getAppString(
227
- "capacitor_background_geolocation_notification_color",
228
- null,
229
- getApplicationContext()
230
- );
231
- if (color != null) {
232
- builder.setColor(Color.parseColor(color));
233
- }
234
- } catch (Exception e) {
235
- Logger.error("Could not set notification color", e);
61
+ @Override
62
+ public void onDestroy() {
63
+ if (client != null && locationCallback != null) {
64
+ client.removeUpdates(locationCallback);
65
+ }
66
+ super.onDestroy();
67
+ releaseMediaPlayer();
236
68
  }
237
69
 
238
- Intent launchIntent = getApplicationContext()
239
- .getPackageManager()
240
- .getLaunchIntentForPackage(getApplicationContext().getPackageName());
241
- if (launchIntent != null) {
242
- launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
243
- builder.setContentIntent(
244
- PendingIntent.getActivity(
245
- getApplicationContext(),
246
- 0,
247
- launchIntent,
248
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE
249
- )
250
- );
70
+ private void releaseMediaPlayer() {
71
+ if (mediaPlayer == null) {
72
+ return;
73
+ }
74
+ try {
75
+ if (mediaPlayer.isPlaying()) {
76
+ mediaPlayer.stop();
77
+ }
78
+ mediaPlayer.release();
79
+ } catch (Exception e) {
80
+ Logger.error("Error releasing MediaPlayer", e);
81
+ }
82
+ mediaPlayer = null;
251
83
  }
252
84
 
253
- // Set the Channel ID for Android O.
254
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
255
- builder.setChannelId(
256
- BackgroundGeolocationService.class.getPackage().getName()
257
- );
85
+ // Handles requests from the activity.
86
+ public class LocalBinder extends Binder {
87
+
88
+ void start(final String id, final String notificationTitle, final String notificationMessage, float distanceFilter) {
89
+ releaseMediaPlayer();
90
+ client = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
91
+ callbackId = id;
92
+
93
+ locationCallback = (location) -> {
94
+ if (mediaPlayer != null) {
95
+ double[] point = { location.getLongitude(), location.getLatitude() };
96
+ var offRoute = distancePointToRoute(point) > distanceThreshold;
97
+ if (offRoute == true && isOffRoute == false) {
98
+ mediaPlayer.start();
99
+ }
100
+ isOffRoute = offRoute;
101
+ }
102
+ Intent intent = new Intent(ACTION_BROADCAST);
103
+ intent.putExtra("location", location);
104
+ intent.putExtra("id", callbackId);
105
+ LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
106
+ };
107
+
108
+ try {
109
+ client.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, distanceFilter, locationCallback);
110
+ } catch (SecurityException ignore) {
111
+ // According to Android Studio, this method can throw a Security Exception if
112
+ // permissions are not yet granted. Rather than check the permissions, which is fiddly,
113
+ // we simply ignore the exception.
114
+ }
115
+
116
+ // Promote the service to the foreground if necessary.
117
+ // Ideally we would only call 'startForeground' if the service is not already
118
+ // foregrounded. Unfortunately, 'getForegroundServiceType' was only introduced
119
+ // in API level 29 and seems to behave weirdly, as reported in #120. However,
120
+ // it appears that 'startForeground' is idempotent, so we just call it repeatedly
121
+ // each time a background watcher is added.
122
+ try {
123
+ // This method has been known to fail due to weird
124
+ // permission bugs, so we prevent any exceptions from
125
+ // crashing the app. See issue #86.
126
+ startForeground(NOTIFICATION_ID, createBackgroundNotification(notificationTitle, notificationMessage));
127
+ } catch (Exception exception) {
128
+ Logger.error("Failed to foreground service", exception);
129
+ }
130
+ }
131
+
132
+ String stop() {
133
+ client.removeUpdates(locationCallback);
134
+ stopForeground(true);
135
+ stopSelf();
136
+ releaseMediaPlayer();
137
+ return callbackId;
138
+ }
139
+
140
+ void setPlannedRoute(String filePath, double[][] routeCoordinates, float distance) {
141
+ route = routeCoordinates;
142
+ distanceThreshold = distance;
143
+ isOffRoute = true;
144
+ try {
145
+ if (mediaPlayer != null) {
146
+ return;
147
+ }
148
+ mediaPlayer = new MediaPlayer();
149
+ AssetManager am = getApplicationContext().getResources().getAssets();
150
+ AssetFileDescriptor assetFileDescriptor = am.openFd("public/" + filePath);
151
+
152
+ mediaPlayer.setDataSource(
153
+ assetFileDescriptor.getFileDescriptor(),
154
+ assetFileDescriptor.getStartOffset(),
155
+ assetFileDescriptor.getLength()
156
+ );
157
+ mediaPlayer.setLooping(false);
158
+
159
+ mediaPlayer.setOnErrorListener((mp, what, extra) -> {
160
+ Logger.error("MediaPlayer error: what=" + what + ", extra=" + extra);
161
+ releaseMediaPlayer();
162
+ return true; // Indicate we handled the error
163
+ });
164
+
165
+ mediaPlayer.prepareAsync();
166
+ } catch (Exception e) {
167
+ Logger.error("PlaySound: Unexpected error", e);
168
+ releaseMediaPlayer();
169
+ }
170
+ }
258
171
  }
259
172
 
260
- return builder.build();
261
- }
262
-
263
- // Gets the identifier of the app's resource by name, returning 0 if not found.
264
- private static int getAppResourceIdentifier(
265
- String name,
266
- String defType,
267
- Context context
268
- ) {
269
- return context
270
- .getResources()
271
- .getIdentifier(name, defType, context.getPackageName());
272
- }
273
-
274
- // Gets a string from the app's strings.xml file, resorting to a fallback if it is not defined.
275
- public static String getAppString(
276
- String name,
277
- String fallback,
278
- Context context
279
- ) {
280
- int id = getAppResourceIdentifier(name, "string", context);
281
- return id == 0 ? fallback : context.getString(id);
282
- }
283
-
284
- private static double haversine(double[] point1, double[] point2) {
285
- double lon1 = point1[0];
286
- double lat1 = point1[1];
287
- double lon2 = point2[0];
288
- double lat2 = point2[1];
289
-
290
- double dLat = Math.toRadians(lat2 - lat1);
291
- double dLon = Math.toRadians(lon2 - lon1);
292
-
293
- double a =
294
- Math.sin(dLat / 2) * Math.sin(dLat / 2) +
295
- Math.cos(Math.toRadians(lat1)) *
296
- Math.cos(Math.toRadians(lat2)) *
297
- Math.sin(dLon / 2) *
298
- Math.sin(dLon / 2);
299
-
300
- double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
301
-
302
- return EARTH_RADIUS_M * c;
303
- }
304
-
305
- private static double distancePointToLineSegment(
306
- double[] point,
307
- double[] lineStart,
308
- double[] lineEnd
309
- ) {
310
- // Calculate the distances between the three points using Haversine
311
- double dist_A_B = haversine(point, lineStart);
312
- double dist_A_C = haversine(point, lineEnd);
313
- double dist_B_C = haversine(lineStart, lineEnd);
314
-
315
- // Handle the edge case where the line segment is a single point
316
- if (dist_B_C == 0) {
317
- return dist_A_B;
173
+ private Notification createBackgroundNotification(String backgroundTitle, String backgroundMessage) {
174
+ Notification.Builder builder = new Notification.Builder(getApplicationContext())
175
+ .setContentTitle(backgroundTitle)
176
+ .setContentText(backgroundMessage)
177
+ .setOngoing(true)
178
+ .setPriority(Notification.PRIORITY_HIGH)
179
+ .setWhen(System.currentTimeMillis());
180
+
181
+ try {
182
+ String name = getAppString("capacitor_background_geolocation_notification_icon", "mipmap/ic_launcher", getApplicationContext());
183
+ String[] parts = name.split("/");
184
+ // It is actually necessary to set a valid icon for the notification to behave
185
+ // correctly when tapped. If there is no icon specified, tapping it will open the
186
+ // app's settings, rather than bringing the application to the foreground.
187
+ builder.setSmallIcon(getAppResourceIdentifier(parts[1], parts[0], getApplicationContext()));
188
+ } catch (Exception e) {
189
+ Logger.error("Could not set notification icon", e);
190
+ }
191
+
192
+ try {
193
+ String color = getAppString("capacitor_background_geolocation_notification_color", null, getApplicationContext());
194
+ if (color != null) {
195
+ builder.setColor(Color.parseColor(color));
196
+ }
197
+ } catch (Exception e) {
198
+ Logger.error("Could not set notification color", e);
199
+ }
200
+
201
+ Intent launchIntent = getApplicationContext()
202
+ .getPackageManager()
203
+ .getLaunchIntentForPackage(getApplicationContext().getPackageName());
204
+ if (launchIntent != null) {
205
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
206
+ builder.setContentIntent(
207
+ PendingIntent.getActivity(
208
+ getApplicationContext(),
209
+ 0,
210
+ launchIntent,
211
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE
212
+ )
213
+ );
214
+ }
215
+
216
+ // Set the Channel ID for Android O.
217
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
218
+ builder.setChannelId(BackgroundGeolocationService.class.getPackage().getName());
219
+ }
220
+
221
+ return builder.build();
318
222
  }
319
223
 
320
- // Check if the angles at the line segment's endpoints are obtuse.
321
- // We use the Law of Cosines (c^2 = a^2 + b^2 - 2ab*cos(C))
322
- // If cos(C) < 0, the angle is obtuse.
323
-
324
- // Angle at B (lineStart)
325
- // Use a small epsilon to handle floating point inaccuracies in division by zero
326
- double cos_B =
327
- (Math.pow(dist_A_B, 2) + Math.pow(dist_B_C, 2) - Math.pow(dist_A_C, 2)) /
328
- (2 * dist_A_B * dist_B_C);
329
- if (cos_B < 0) {
330
- return dist_A_B;
224
+ // Gets the identifier of the app's resource by name, returning 0 if not found.
225
+ private static int getAppResourceIdentifier(String name, String defType, Context context) {
226
+ return context.getResources().getIdentifier(name, defType, context.getPackageName());
331
227
  }
332
228
 
333
- // Angle at C (lineEnd)
334
- double cos_C =
335
- (Math.pow(dist_A_C, 2) + Math.pow(dist_B_C, 2) - Math.pow(dist_A_B, 2)) /
336
- (2 * dist_A_C * dist_B_C);
337
- if (cos_C < 0) {
338
- return dist_A_C;
229
+ // Gets a string from the app's strings.xml file, resorting to a fallback if it is not defined.
230
+ public static String getAppString(String name, String fallback, Context context) {
231
+ int id = getAppResourceIdentifier(name, "string", context);
232
+ return id == 0 ? fallback : context.getString(id);
339
233
  }
340
234
 
341
- // If both angles are acute, the closest point is on the line segment itself.
342
- // We can calculate the distance (height of the triangle) using its area.
343
-
344
- // 1. Calculate the semi-perimeter of the triangle ABC
345
- double s = (dist_A_B + dist_A_C + dist_B_C) / 2;
346
-
347
- // 2. Calculate the area using Heron's formula
348
- double area = Math.sqrt(
349
- Math.max(0, s * (s - dist_A_B) * (s - dist_A_C) * (s - dist_B_C))
350
- );
351
-
352
- // 3. The distance is the height of the triangle from point A to the base BC
353
- // Area = 0.5 * base * height => height = 2 * Area / base
354
- return (2 * area) / dist_B_C;
355
- }
356
-
357
- public double distancePointToRoute(double[] point) {
358
- // If the polyline has less than 2 points, we can't form a segment.
359
- if (this.route.length < 2) {
360
- if (this.route.length == 1) {
361
- return haversine(point, this.route[0]);
362
- }
363
- return Double.POSITIVE_INFINITY; // No line segments to measure against
235
+ private static double haversine(double[] point1, double[] point2) {
236
+ double lon1 = point1[0];
237
+ double lat1 = point1[1];
238
+ double lon2 = point2[0];
239
+ double lat2 = point2[1];
240
+
241
+ double dLat = Math.toRadians(lat2 - lat1);
242
+ double dLon = Math.toRadians(lon2 - lon1);
243
+
244
+ double a =
245
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
246
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
247
+
248
+ double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
249
+
250
+ return EARTH_RADIUS_M * c;
364
251
  }
365
252
 
366
- double minDistance = Double.POSITIVE_INFINITY;
253
+ private static double distancePointToLineSegment(double[] point, double[] lineStart, double[] lineEnd) {
254
+ // Calculate the distances between the three points using Haversine
255
+ double dist_A_B = haversine(point, lineStart);
256
+ double dist_A_C = haversine(point, lineEnd);
257
+ double dist_B_C = haversine(lineStart, lineEnd);
258
+
259
+ // Handle the edge case where the line segment is a single point
260
+ if (dist_B_C == 0) {
261
+ return dist_A_B;
262
+ }
263
+
264
+ // Check if the angles at the line segment's endpoints are obtuse.
265
+ // We use the Law of Cosines (c^2 = a^2 + b^2 - 2ab*cos(C))
266
+ // If cos(C) < 0, the angle is obtuse.
267
+
268
+ // Angle at B (lineStart)
269
+ // Use a small epsilon to handle floating point inaccuracies in division by zero
270
+ double cos_B = (Math.pow(dist_A_B, 2) + Math.pow(dist_B_C, 2) - Math.pow(dist_A_C, 2)) / (2 * dist_A_B * dist_B_C);
271
+ if (cos_B < 0) {
272
+ return dist_A_B;
273
+ }
367
274
 
368
- for (int i = 0; i < this.route.length - 1; i++) {
369
- double[] lineStart = this.route[i];
370
- double[] lineEnd = this.route[i + 1];
371
- double distance = distancePointToLineSegment(point, lineStart, lineEnd);
372
- if (distance < minDistance) {
373
- minDistance = distance;
374
- }
275
+ // Angle at C (lineEnd)
276
+ double cos_C = (Math.pow(dist_A_C, 2) + Math.pow(dist_B_C, 2) - Math.pow(dist_A_B, 2)) / (2 * dist_A_C * dist_B_C);
277
+ if (cos_C < 0) {
278
+ return dist_A_C;
279
+ }
280
+
281
+ // If both angles are acute, the closest point is on the line segment itself.
282
+ // We can calculate the distance (height of the triangle) using its area.
283
+
284
+ // 1. Calculate the semi-perimeter of the triangle ABC
285
+ double s = (dist_A_B + dist_A_C + dist_B_C) / 2;
286
+
287
+ // 2. Calculate the area using Heron's formula
288
+ double area = Math.sqrt(Math.max(0, s * (s - dist_A_B) * (s - dist_A_C) * (s - dist_B_C)));
289
+
290
+ // 3. The distance is the height of the triangle from point A to the base BC
291
+ // Area = 0.5 * base * height => height = 2 * Area / base
292
+ return (2 * area) / dist_B_C;
375
293
  }
376
294
 
377
- return minDistance;
378
- }
295
+ public double distancePointToRoute(double[] point) {
296
+ // If the polyline has less than 2 points, we can't form a segment.
297
+ if (this.route.length < 2) {
298
+ if (this.route.length == 1) {
299
+ return haversine(point, this.route[0]);
300
+ }
301
+ return Double.POSITIVE_INFINITY; // No line segments to measure against
302
+ }
303
+
304
+ double minDistance = Double.POSITIVE_INFINITY;
305
+
306
+ for (int i = 0; i < this.route.length - 1; i++) {
307
+ double[] lineStart = this.route[i];
308
+ double[] lineEnd = this.route[i + 1];
309
+ double distance = distancePointToLineSegment(point, lineStart, lineEnd);
310
+ if (distance < minDistance) {
311
+ minDistance = distance;
312
+ }
313
+ }
314
+
315
+ return minDistance;
316
+ }
379
317
  }