@fleetbase/fleetops-engine 0.2.5 → 0.2.7

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.
Files changed (325) hide show
  1. package/.gitattributes +14 -0
  2. package/.php-cs-fixer.cache +1 -0
  3. package/.php-cs-fixer.php +29 -0
  4. package/LICENSE.md +16 -4
  5. package/README.md +103 -13
  6. package/addon/components/admin/visibility-controls.hbs +12 -0
  7. package/addon/components/admin/visibility-controls.js +82 -0
  8. package/addon/components/fleet-panel.js +13 -4
  9. package/addon/components/fleet-vehicle-listing.hbs +1 -1
  10. package/addon/components/layout/fleet-ops-sidebar.hbs +26 -0
  11. package/addon/components/layout/fleet-ops-sidebar.js +129 -0
  12. package/addon/engine.js +19 -0
  13. package/addon/helpers/is-section-visible.js +5 -0
  14. package/addon/routes/application.js +6 -2
  15. package/addon/routes/virtual.js +10 -0
  16. package/addon/routes.js +1 -0
  17. package/addon/templates/application.hbs +1 -19
  18. package/addon/templates/virtual.hbs +9 -0
  19. package/app/components/admin/visibility-controls.js +1 -0
  20. package/app/components/layout/fleet-ops-sidebar.js +1 -0
  21. package/app/helpers/is-section-visible.js +1 -0
  22. package/app/routes/virtual.js +1 -0
  23. package/app/templates/virtual.js +1 -0
  24. package/composer.json +85 -0
  25. package/extension.json +10 -0
  26. package/package.json +6 -6
  27. package/phpstan.neon.dist +8 -0
  28. package/phpunit.xml.dist +16 -0
  29. package/server/config/api.php +765 -0
  30. package/server/config/cache.stores.php +18 -0
  31. package/server/config/dompdf.php +242 -0
  32. package/server/config/fleetops.php +19 -0
  33. package/server/config/geocoder.php +108 -0
  34. package/server/data/globe.json +261 -0
  35. package/server/migrations/2023_04_27_053456_create_contacts_table.php +49 -0
  36. package/server/migrations/2023_04_27_053456_create_drivers_table.php +62 -0
  37. package/server/migrations/2023_04_27_053456_create_entities_table.php +67 -0
  38. package/server/migrations/2023_04_27_053456_create_fleet_drivers_table.php +39 -0
  39. package/server/migrations/2023_04_27_053456_create_fleets_table.php +47 -0
  40. package/server/migrations/2023_04_27_053456_create_fuel_reports_table.php +50 -0
  41. package/server/migrations/2023_04_27_053456_create_integrated_vendors_table.php +44 -0
  42. package/server/migrations/2023_04_27_053456_create_issues_table.php +53 -0
  43. package/server/migrations/2023_04_27_053456_create_orders_table.php +70 -0
  44. package/server/migrations/2023_04_27_053456_create_payloads_table.php +51 -0
  45. package/server/migrations/2023_04_27_053456_create_places_table.php +62 -0
  46. package/server/migrations/2023_04_27_053456_create_positions_table.php +46 -0
  47. package/server/migrations/2023_04_27_053456_create_proofs_table.php +44 -0
  48. package/server/migrations/2023_04_27_053456_create_purchase_rates_table.php +46 -0
  49. package/server/migrations/2023_04_27_053456_create_routes_table.php +40 -0
  50. package/server/migrations/2023_04_27_053456_create_service_areas_table.php +47 -0
  51. package/server/migrations/2023_04_27_053456_create_service_quote_items_table.php +42 -0
  52. package/server/migrations/2023_04_27_053456_create_service_quotes_table.php +47 -0
  53. package/server/migrations/2023_04_27_053456_create_service_rate_fees_table.php +45 -0
  54. package/server/migrations/2023_04_27_053456_create_service_rate_parcel_fees_table.php +47 -0
  55. package/server/migrations/2023_04_27_053456_create_service_rates_table.php +61 -0
  56. package/server/migrations/2023_04_27_053456_create_tracking_numbers_table.php +46 -0
  57. package/server/migrations/2023_04_27_053456_create_tracking_statuses_table.php +54 -0
  58. package/server/migrations/2023_04_27_053456_create_vehicles_table.php +84 -0
  59. package/server/migrations/2023_04_27_053456_create_vendors_table.php +56 -0
  60. package/server/migrations/2023_04_27_053456_create_waypoints_table.php +45 -0
  61. package/server/migrations/2023_04_27_053456_create_zones_table.php +46 -0
  62. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_companies_table.php +32 -0
  63. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_contacts_table.php +34 -0
  64. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_drivers_table.php +40 -0
  65. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_entities_table.php +40 -0
  66. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_fleet_drivers_table.php +34 -0
  67. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_fleets_table.php +36 -0
  68. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_fuel_reports_table.php +36 -0
  69. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_integrated_vendors_table.php +34 -0
  70. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_issues_table.php +38 -0
  71. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_orders_table.php +48 -0
  72. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_payloads_table.php +40 -0
  73. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_places_table.php +32 -0
  74. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_positions_table.php +36 -0
  75. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_proofs_table.php +34 -0
  76. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_purchase_rates_table.php +38 -0
  77. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_routes_table.php +34 -0
  78. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_areas_table.php +34 -0
  79. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_quote_items_table.php +32 -0
  80. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_quotes_table.php +38 -0
  81. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_rate_fees_table.php +32 -0
  82. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_rate_parcel_fees_table.php +32 -0
  83. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_service_rates_table.php +36 -0
  84. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_tracking_numbers_table.php +34 -0
  85. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_tracking_statuses_table.php +34 -0
  86. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_vehicles_table.php +36 -0
  87. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_vendors_table.php +40 -0
  88. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_waypoints_table.php +38 -0
  89. package/server/migrations/2023_04_27_053459_add_foreign_keys_to_zones_table.php +34 -0
  90. package/server/migrations/2023_09_14_074512_update_vehicles_table.php +100 -0
  91. package/server/migrations/2023_09_14_074619_create_vehicle_devices_table.php +44 -0
  92. package/server/migrations/2023_09_26_071205_add_online_column_to_vehicles_table.php +32 -0
  93. package/server/migrations/2023_09_26_083957_create_fleet_vehicles_table.php +45 -0
  94. package/server/migrations/2023_09_27_023757_add_column_to_fleets_table.php +46 -0
  95. package/server/migrations/2023_10_05_074811_update_vehicle.php +42 -0
  96. package/server/resources/views/labels/default.php +165 -0
  97. package/server/src/Auth/Schemas/FleetOps.php +75 -0
  98. package/server/src/Casts/MultiPolygon.php +50 -0
  99. package/server/src/Casts/Point.php +109 -0
  100. package/server/src/Casts/Polygon.php +46 -0
  101. package/server/src/Console/Commands/DispatchAdhocOrders.php +137 -0
  102. package/server/src/Console/Commands/DispatchOrders.php +71 -0
  103. package/server/src/Console/Commands/TrackOrderDistanceAndTime.php +62 -0
  104. package/server/src/Events/DriverLocationChanged.php +174 -0
  105. package/server/src/Events/DriverSimulatedLocationChanged.php +183 -0
  106. package/server/src/Events/EntityDriverAssigned.php +36 -0
  107. package/server/src/Events/OrderCanceled.php +15 -0
  108. package/server/src/Events/OrderCompleted.php +15 -0
  109. package/server/src/Events/OrderDispatchFailed.php +42 -0
  110. package/server/src/Events/OrderDispatched.php +15 -0
  111. package/server/src/Events/OrderDriverAssigned.php +15 -0
  112. package/server/src/Events/OrderReady.php +15 -0
  113. package/server/src/Events/OrderStarted.php +15 -0
  114. package/server/src/Exceptions/IntegratedVendorException.php +18 -0
  115. package/server/src/Expansions/UserExpansion.php +63 -0
  116. package/server/src/Exports/ContactExport.php +56 -0
  117. package/server/src/Exports/DriverExport.php +61 -0
  118. package/server/src/Exports/FleetExport.php +53 -0
  119. package/server/src/Exports/PlaceExport.php +55 -0
  120. package/server/src/Exports/ServiceAreaExport.php +58 -0
  121. package/server/src/Exports/VehicleExport.php +57 -0
  122. package/server/src/Exports/VendorExport.php +57 -0
  123. package/server/src/Http/Controllers/Api/v1/ContactController.php +133 -0
  124. package/server/src/Http/Controllers/Api/v1/DriverController.php +691 -0
  125. package/server/src/Http/Controllers/Api/v1/EntityController.php +230 -0
  126. package/server/src/Http/Controllers/Api/v1/FleetController.php +146 -0
  127. package/server/src/Http/Controllers/Api/v1/LabelController.php +63 -0
  128. package/server/src/Http/Controllers/Api/v1/OrderController.php +1114 -0
  129. package/server/src/Http/Controllers/Api/v1/PayloadController.php +195 -0
  130. package/server/src/Http/Controllers/Api/v1/PlaceController.php +333 -0
  131. package/server/src/Http/Controllers/Api/v1/PurchaseRateController.php +199 -0
  132. package/server/src/Http/Controllers/Api/v1/ServiceAreaController.php +133 -0
  133. package/server/src/Http/Controllers/Api/v1/ServiceQuoteController.php +381 -0
  134. package/server/src/Http/Controllers/Api/v1/ServiceRateController.php +223 -0
  135. package/server/src/Http/Controllers/Api/v1/TrackingNumberController.php +146 -0
  136. package/server/src/Http/Controllers/Api/v1/TrackingStatusController.php +138 -0
  137. package/server/src/Http/Controllers/Api/v1/VehicleController.php +191 -0
  138. package/server/src/Http/Controllers/Api/v1/VendorController.php +159 -0
  139. package/server/src/Http/Controllers/Api/v1/ZoneController.php +146 -0
  140. package/server/src/Http/Controllers/FleetOpsController.php +13 -0
  141. package/server/src/Http/Controllers/Internal/v1/ContactController.php +69 -0
  142. package/server/src/Http/Controllers/Internal/v1/DriverController.php +261 -0
  143. package/server/src/Http/Controllers/Internal/v1/EntityController.php +15 -0
  144. package/server/src/Http/Controllers/Internal/v1/FleetController.php +152 -0
  145. package/server/src/Http/Controllers/Internal/v1/FleetOpsLookupController.php +80 -0
  146. package/server/src/Http/Controllers/Internal/v1/FuelReportController.php +15 -0
  147. package/server/src/Http/Controllers/Internal/v1/GeocoderController.php +96 -0
  148. package/server/src/Http/Controllers/Internal/v1/IntegratedVendorController.php +63 -0
  149. package/server/src/Http/Controllers/Internal/v1/IssueController.php +15 -0
  150. package/server/src/Http/Controllers/Internal/v1/LiveController.php +127 -0
  151. package/server/src/Http/Controllers/Internal/v1/MetricsController.php +75 -0
  152. package/server/src/Http/Controllers/Internal/v1/MorphController.php +77 -0
  153. package/server/src/Http/Controllers/Internal/v1/OrderConfigController.php +269 -0
  154. package/server/src/Http/Controllers/Internal/v1/OrderController.php +564 -0
  155. package/server/src/Http/Controllers/Internal/v1/PayloadController.php +15 -0
  156. package/server/src/Http/Controllers/Internal/v1/PlaceController.php +163 -0
  157. package/server/src/Http/Controllers/Internal/v1/ProofController.php +139 -0
  158. package/server/src/Http/Controllers/Internal/v1/PurchaseRateController.php +15 -0
  159. package/server/src/Http/Controllers/Internal/v1/RouteController.php +15 -0
  160. package/server/src/Http/Controllers/Internal/v1/ServiceAreaController.php +32 -0
  161. package/server/src/Http/Controllers/Internal/v1/ServiceQuoteController.php +345 -0
  162. package/server/src/Http/Controllers/Internal/v1/ServiceRateController.php +47 -0
  163. package/server/src/Http/Controllers/Internal/v1/SettingController.php +30 -0
  164. package/server/src/Http/Controllers/Internal/v1/TrackingNumberController.php +15 -0
  165. package/server/src/Http/Controllers/Internal/v1/TrackingStatusController.php +15 -0
  166. package/server/src/Http/Controllers/Internal/v1/VehicleController.php +65 -0
  167. package/server/src/Http/Controllers/Internal/v1/VehicleDeviceController.php +15 -0
  168. package/server/src/Http/Controllers/Internal/v1/VendorController.php +120 -0
  169. package/server/src/Http/Controllers/Internal/v1/ZoneController.php +15 -0
  170. package/server/src/Http/Filter/ContactFilter.php +86 -0
  171. package/server/src/Http/Filter/DriverFilter.php +148 -0
  172. package/server/src/Http/Filter/FleetFilter.php +108 -0
  173. package/server/src/Http/Filter/IntegratedVendorFilter.php +13 -0
  174. package/server/src/Http/Filter/OrderFilter.php +242 -0
  175. package/server/src/Http/Filter/PlaceFilter.php +111 -0
  176. package/server/src/Http/Filter/ServiceAreaFilter.php +13 -0
  177. package/server/src/Http/Filter/ServiceQuoteFilter.php +13 -0
  178. package/server/src/Http/Filter/ServiceRateFilter.php +33 -0
  179. package/server/src/Http/Filter/TrackingStatusFilter.php +43 -0
  180. package/server/src/Http/Filter/VehicleFilter.php +91 -0
  181. package/server/src/Http/Filter/VendorFilter.php +83 -0
  182. package/server/src/Http/Filter/ZoneFilter.php +13 -0
  183. package/server/src/Http/Requests/CancelOrderRequest.php +31 -0
  184. package/server/src/Http/Requests/CreateContactRequest.php +33 -0
  185. package/server/src/Http/Requests/CreateDriverRequest.php +55 -0
  186. package/server/src/Http/Requests/CreateEntityRequest.php +43 -0
  187. package/server/src/Http/Requests/CreateFleetRequest.php +32 -0
  188. package/server/src/Http/Requests/CreateOrderRequest.php +66 -0
  189. package/server/src/Http/Requests/CreatePayloadRequest.php +37 -0
  190. package/server/src/Http/Requests/CreatePlaceRequest.php +38 -0
  191. package/server/src/Http/Requests/CreatePurchaseRateRequest.php +33 -0
  192. package/server/src/Http/Requests/CreateServiceAreaRequest.php +35 -0
  193. package/server/src/Http/Requests/CreateServiceQuoteRequest.php +29 -0
  194. package/server/src/Http/Requests/CreateServiceRateRequest.php +56 -0
  195. package/server/src/Http/Requests/CreateTrackingNumberRequest.php +34 -0
  196. package/server/src/Http/Requests/CreateTrackingStatusRequest.php +42 -0
  197. package/server/src/Http/Requests/CreateVehicleRequest.php +32 -0
  198. package/server/src/Http/Requests/CreateVendorRequest.php +35 -0
  199. package/server/src/Http/Requests/CreateZoneRequest.php +35 -0
  200. package/server/src/Http/Requests/DecodeTrackingNumberQR.php +30 -0
  201. package/server/src/Http/Requests/DriverSimulationRequest.php +46 -0
  202. package/server/src/Http/Requests/Internal/AssignOrderRequest.php +31 -0
  203. package/server/src/Http/Requests/Internal/CreateDriverRequest.php +42 -0
  204. package/server/src/Http/Requests/Internal/FleetActionRequest.php +32 -0
  205. package/server/src/Http/Requests/Internal/UpdateDriverRequest.php +7 -0
  206. package/server/src/Http/Requests/QueryServiceQuotesRequest.php +41 -0
  207. package/server/src/Http/Requests/UpdateContactRequest.php +20 -0
  208. package/server/src/Http/Requests/UpdateDriverRequest.php +7 -0
  209. package/server/src/Http/Requests/UpdateEntityRequest.php +7 -0
  210. package/server/src/Http/Requests/UpdateFleetRequest.php +7 -0
  211. package/server/src/Http/Requests/UpdateOrderRequest.php +7 -0
  212. package/server/src/Http/Requests/UpdatePayloadRequest.php +7 -0
  213. package/server/src/Http/Requests/UpdatePlaceRequest.php +7 -0
  214. package/server/src/Http/Requests/UpdatePurchaseRateRequest.php +7 -0
  215. package/server/src/Http/Requests/UpdateServiceAreaRequest.php +29 -0
  216. package/server/src/Http/Requests/UpdateServiceQuoteRequest.php +29 -0
  217. package/server/src/Http/Requests/UpdateServiceRateRequest.php +29 -0
  218. package/server/src/Http/Requests/UpdateTrackingNumberRequest.php +7 -0
  219. package/server/src/Http/Requests/UpdateTrackingStatusRequest.php +32 -0
  220. package/server/src/Http/Requests/UpdateVehicleRequest.php +7 -0
  221. package/server/src/Http/Requests/UpdateVendorRequest.php +7 -0
  222. package/server/src/Http/Requests/UpdateZoneRequest.php +7 -0
  223. package/server/src/Http/Resources/Internal/v1/IntegratedVendorFacilitator.php +37 -0
  224. package/server/src/Http/Resources/Internal/v1/Vehicle.php +33 -0
  225. package/server/src/Http/Resources/Internal/v1/Waypoint.php +34 -0
  226. package/server/src/Http/Resources/v1/Contact.php +62 -0
  227. package/server/src/Http/Resources/v1/CurrentJob.php +36 -0
  228. package/server/src/Http/Resources/v1/DeletedResource.php +61 -0
  229. package/server/src/Http/Resources/v1/Driver.php +89 -0
  230. package/server/src/Http/Resources/v1/Entity.php +107 -0
  231. package/server/src/Http/Resources/v1/Fleet.php +60 -0
  232. package/server/src/Http/Resources/v1/FuelReport.php +67 -0
  233. package/server/src/Http/Resources/v1/Order.php +131 -0
  234. package/server/src/Http/Resources/v1/Payload.php +94 -0
  235. package/server/src/Http/Resources/v1/Place.php +84 -0
  236. package/server/src/Http/Resources/v1/Proof.php +36 -0
  237. package/server/src/Http/Resources/v1/PurchaseRate.php +77 -0
  238. package/server/src/Http/Resources/v1/ServiceArea.php +55 -0
  239. package/server/src/Http/Resources/v1/ServiceQuote.php +57 -0
  240. package/server/src/Http/Resources/v1/ServiceRate.php +94 -0
  241. package/server/src/Http/Resources/v1/ServiceRateFee.php +58 -0
  242. package/server/src/Http/Resources/v1/ServiceRateParcelFee.php +48 -0
  243. package/server/src/Http/Resources/v1/TrackingNumber.php +62 -0
  244. package/server/src/Http/Resources/v1/TrackingStatus.php +84 -0
  245. package/server/src/Http/Resources/v1/Vehicle.php +75 -0
  246. package/server/src/Http/Resources/v1/VehicleDevice.php +44 -0
  247. package/server/src/Http/Resources/v1/VehicleWithoutDriver.php +72 -0
  248. package/server/src/Http/Resources/v1/Vendor.php +72 -0
  249. package/server/src/Http/Resources/v1/Zone.php +60 -0
  250. package/server/src/Imports/OrdersImport.php +18 -0
  251. package/server/src/Integrations/Lalamove/Lalamove.php +678 -0
  252. package/server/src/Integrations/Lalamove/LalamoveDeliveryStop.php +97 -0
  253. package/server/src/Integrations/Lalamove/LalamoveMarket.php +123 -0
  254. package/server/src/Integrations/Lalamove/LalamoveServiceType.php +161 -0
  255. package/server/src/Jobs/SimulateDrivingRoute.php +81 -0
  256. package/server/src/Jobs/SimulateWaypointReached.php +79 -0
  257. package/server/src/Listeners/HandleOrderCanceled.php +46 -0
  258. package/server/src/Listeners/HandleOrderCreated.php +23 -0
  259. package/server/src/Listeners/HandleOrderDispatchFailed.php +35 -0
  260. package/server/src/Listeners/HandleOrderDispatched.php +108 -0
  261. package/server/src/Listeners/HandleOrderDriverAssigned.php +42 -0
  262. package/server/src/Listeners/HandleOrderReady.php +27 -0
  263. package/server/src/Listeners/HandleUserRemovedFromCompany.php +31 -0
  264. package/server/src/Models/Contact.php +239 -0
  265. package/server/src/Models/Driver.php +543 -0
  266. package/server/src/Models/Entity.php +416 -0
  267. package/server/src/Models/Fleet.php +203 -0
  268. package/server/src/Models/FleetDriver.php +73 -0
  269. package/server/src/Models/FleetVehicle.php +73 -0
  270. package/server/src/Models/FuelReport.php +125 -0
  271. package/server/src/Models/IntegratedVendor.php +219 -0
  272. package/server/src/Models/Issue.php +111 -0
  273. package/server/src/Models/Order.php +1132 -0
  274. package/server/src/Models/Payload.php +699 -0
  275. package/server/src/Models/Place.php +656 -0
  276. package/server/src/Models/Position.php +119 -0
  277. package/server/src/Models/Proof.php +94 -0
  278. package/server/src/Models/PurchaseRate.php +208 -0
  279. package/server/src/Models/Route.php +128 -0
  280. package/server/src/Models/ServiceArea.php +161 -0
  281. package/server/src/Models/ServiceQuote.php +207 -0
  282. package/server/src/Models/ServiceQuoteItem.php +46 -0
  283. package/server/src/Models/ServiceRate.php +953 -0
  284. package/server/src/Models/ServiceRateFee.php +96 -0
  285. package/server/src/Models/ServiceRateParcelFee.php +116 -0
  286. package/server/src/Models/TrackingNumber.php +310 -0
  287. package/server/src/Models/TrackingStatus.php +177 -0
  288. package/server/src/Models/Vehicle.php +452 -0
  289. package/server/src/Models/VehicleDevice.php +79 -0
  290. package/server/src/Models/Vendor.php +244 -0
  291. package/server/src/Models/Waypoint.php +226 -0
  292. package/server/src/Models/Zone.php +95 -0
  293. package/server/src/Notifications/OrderAssigned.php +140 -0
  294. package/server/src/Notifications/OrderCanceled.php +129 -0
  295. package/server/src/Notifications/OrderDispatchFailed.php +81 -0
  296. package/server/src/Notifications/OrderDispatched.php +173 -0
  297. package/server/src/Notifications/OrderPing.php +162 -0
  298. package/server/src/Notifications/OrderSplit.php +56 -0
  299. package/server/src/Observers/DriverObserver.php +34 -0
  300. package/server/src/Observers/OrderObserver.php +34 -0
  301. package/server/src/Observers/PayloadObserver.php +30 -0
  302. package/server/src/Observers/PlaceObserver.php +26 -0
  303. package/server/src/Observers/PurchaseRateObserver.php +61 -0
  304. package/server/src/Observers/ServiceAreaObserver.php +35 -0
  305. package/server/src/Observers/ServiceRateObserver.php +78 -0
  306. package/server/src/Observers/TrackingNumberObserver.php +44 -0
  307. package/server/src/Observers/UserObserver.php +20 -0
  308. package/server/src/Observers/VehicleObserver.php +66 -0
  309. package/server/src/Providers/EventServiceProvider.php +30 -0
  310. package/server/src/Providers/FleetOpsServiceProvider.php +94 -0
  311. package/server/src/Rules/ResolvablePoint.php +37 -0
  312. package/server/src/Scopes/DriverScope.php +21 -0
  313. package/server/src/Support/Algo.php +55 -0
  314. package/server/src/Support/DistanceMatrix.php +27 -0
  315. package/server/src/Support/Encoding/Polyline.php +168 -0
  316. package/server/src/Support/Flow.php +622 -0
  317. package/server/src/Support/Geocoding.php +216 -0
  318. package/server/src/Support/IntegratedVendors.php +287 -0
  319. package/server/src/Support/Metrics.php +305 -0
  320. package/server/src/Support/OSRM.php +191 -0
  321. package/server/src/Support/ParsePhone.php +21 -0
  322. package/server/src/Support/Utils.php +1145 -0
  323. package/server/src/Traits/HasTrackingNumber.php +185 -0
  324. package/server/src/routes.php +381 -0
  325. package/server/tests/Feature.php +5 -0
@@ -0,0 +1,1145 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\FleetOps\Support;
4
+
5
+ use Fleetbase\Support\Utils as FleetbaseUtils;
6
+ use Illuminate\Support\Arr;
7
+ use Illuminate\Support\Facades\DB;
8
+ use Illuminate\Support\Facades\Http;
9
+ use Illuminate\Support\Facades\Redis;
10
+ use Illuminate\Support\Str;
11
+
12
+ class Utils extends FleetbaseUtils
13
+ {
14
+ /**
15
+ * A constant multiplier used to calculate driving time from distance.
16
+ *
17
+ * @var float
18
+ */
19
+ public const DRIVING_TIME_MULTIPLIER = 7.2;
20
+
21
+ /**
22
+ * Get a formatted string representation of a place's address.
23
+ *
24
+ * @param \Fleetbase\FleetOps\Models\Place $place the place object to retrieve the address from
25
+ * @param bool $useHtml whether to format the address as HTML (default is false)
26
+ * @param array|string $except an array of keys to exclude from the address (default is an empty array)
27
+ *
28
+ * @return string the formatted address string
29
+ */
30
+ public static function getAddressStringForPlace(\Fleetbase\FleetOps\Models\Place $place, $useHtml = false, $except = [])
31
+ {
32
+ $address = $useHtml ? '<address>' : '';
33
+ $parts = collect([
34
+ 'name',
35
+ 'street1',
36
+ 'street2',
37
+ 'city',
38
+ 'province',
39
+ 'postal_code',
40
+ 'country_name',
41
+ ])->filter(function ($part) use ($except) {
42
+ return is_array($except) ? !in_array($part, $except) : true;
43
+ })->values();
44
+ $numberOfParts = $parts->count();
45
+ $addressValues = [];
46
+ $seperator = $useHtml ? '<br>' : ' - ';
47
+
48
+ for ($i = 0; $i < $numberOfParts; $i++) {
49
+ $key = $parts[$i];
50
+ $value = data_get($place, $key);
51
+
52
+ if (is_string($value)) {
53
+ $value = strtoupper($value);
54
+ }
55
+
56
+ // if value empty skip or value equal to last value skip
57
+ if (empty($value) || in_array($value, $addressValues) || (Str::contains(data_get($place, 'street1'), $value) && $key !== 'street1')) {
58
+ continue;
59
+ }
60
+
61
+ $addressValues[$key] = $value;
62
+ }
63
+
64
+ foreach ($addressValues as $key => $value) {
65
+ if ($key === array_key_last($addressValues)) {
66
+ $seperator = '';
67
+ }
68
+
69
+ if ($useHtml && in_array($key, ['street1', 'street2', 'postal_code'])) {
70
+ $seperator = '<br>';
71
+ }
72
+
73
+ $address .= strtoupper($value) . $seperator;
74
+ $seperator = ', ';
75
+ }
76
+
77
+ if ($useHtml) {
78
+ $address .= '</address>';
79
+ }
80
+
81
+ return $address;
82
+ }
83
+
84
+ /**
85
+ * Unpack a binary string representing a geographic point into an array of values.
86
+ *
87
+ * @return array an array of values representing the geographic point, with keys for 'order', 'type', 'lat', and 'lon'
88
+ */
89
+ public static function unpackPoint(string $bindaryString): array
90
+ {
91
+ return unpack('x/x/x/x/corder/Ltype/dlat/dlon', $bindaryString);
92
+ }
93
+
94
+ /**
95
+ * Unpacks a mysql POINT column from binary to array.
96
+ */
97
+ public static function mysqlPointAsGeometry(string $rawPoint): \Grimzy\LaravelMysqlSpatial\Types\Point
98
+ {
99
+ $coordinates = static::unpackPoint($rawPoint);
100
+
101
+ return new \Grimzy\LaravelMysqlSpatial\Types\Point($coordinates['lon'], $coordinates['lat']);
102
+ }
103
+
104
+ /**
105
+ * Determines if a given number is a valid latitude value in the range of -90 to 90 degrees.
106
+ *
107
+ * @param mixed $num the number to check for latitude validity
108
+ *
109
+ * @return bool true if the number is a valid latitude value, false otherwise
110
+ */
111
+ public static function isLatitude($num): bool
112
+ {
113
+ if (!is_numeric($num) || $num === null) {
114
+ return false;
115
+ }
116
+
117
+ // cast to float
118
+ $num = (float) $num;
119
+
120
+ return is_finite($num) && $num >= -90 && $num <= 90;
121
+ }
122
+
123
+ /**
124
+ * Determines if a given number is a valid longitude value in the range of -180 to 180 degrees.
125
+ *
126
+ * @param mixed $num the number to check for longitude validity
127
+ *
128
+ * @return bool true if the number is a valid longitude value, false otherwise
129
+ */
130
+ public static function isLongitude($num): bool
131
+ {
132
+ if (!is_numeric($num) || is_null($num)) {
133
+ return false;
134
+ }
135
+
136
+ // cast to float
137
+ $num = (float) $num;
138
+
139
+ return is_finite($num) && $num >= -180 && $num <= 180;
140
+ }
141
+
142
+ /**
143
+ * Cleans a string containing a coordinate value by removing all non-numeric and non-period characters.
144
+ *
145
+ * @param string $string the string to clean
146
+ *
147
+ * @return string the cleaned string containing only numeric and period characters
148
+ */
149
+ public static function cleanCoordinateString($string)
150
+ {
151
+ return str_replace([' ', ',', ';', ':', '-', '+', '/', '\\', '(', ')', '[', ']', '{', '}', '_', '|', '@', '#', '$', '%', '^', '&', '*', '=', '?', '!', '<', '>', '~', '`', '"', "'", '“', '”'], '', $string);
152
+ }
153
+
154
+ /**
155
+ * Determines whether the given input represents valid coordinates.
156
+ *
157
+ * @param mixed $coordinates the input to check for coordinates validity
158
+ *
159
+ * @return bool true if the input represents valid coordinates, false otherwise
160
+ */
161
+ public static function isCoordinates($coordinates): bool
162
+ {
163
+ try {
164
+ return static::getPointFromMixed($coordinates) instanceof \Grimzy\LaravelMysqlSpatial\Types\Point;
165
+ } catch (\Throwable $e) {
166
+ return false;
167
+ }
168
+
169
+ return false;
170
+ }
171
+
172
+ /**
173
+ * Gets a Point object from coordinates.
174
+ *
175
+ * @param mixed $coordinates the coordinates input value to extract a coordinate from
176
+ *
177
+ * @return \Grimzy\LaravelMysqlSpatial\Types\Point the extracted Point object
178
+ */
179
+ public static function getPointFromMixed($coordinates): ?\Grimzy\LaravelMysqlSpatial\Types\Point
180
+ {
181
+ $latitude = null;
182
+ $longitude = null;
183
+
184
+ if ($coordinates instanceof \Fleetbase\FleetOps\Models\Place) {
185
+ $coordinates = $coordinates->location;
186
+ }
187
+
188
+ if ($coordinates instanceof \Fleetbase\FleetOps\Models\Driver) {
189
+ $coordinates = $coordinates->location;
190
+ }
191
+
192
+ // any model with spatial location point
193
+ if ($coordinates instanceof \Illuminate\Database\Eloquent\Model && $coordinates->isFillable('location')) {
194
+ $coordinates = $coordinates->location;
195
+ }
196
+
197
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression) {
198
+ $coordinates = $coordinates->getSpatialValue();
199
+ }
200
+
201
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
202
+ $latitude = $coordinates->getLat();
203
+ $longitude = $coordinates->getLng();
204
+ } elseif (static::isGeoJson($coordinates)) {
205
+ $coordinatesJson = null;
206
+
207
+ if (is_array($coordinates) || is_object($coordinates)) {
208
+ $coordinatesJson = json_encode($coordinates);
209
+ }
210
+
211
+ if (static::isJson($coordinatesJson)) {
212
+ $coordinates = \Grimzy\LaravelMysqlSpatial\Types\Point::fromJson($coordinatesJson);
213
+ }
214
+ } elseif (is_array($coordinates) || is_object($coordinates)) {
215
+ // if known location based record
216
+ if (static::exists($coordinates, 'public_id')) {
217
+ if (Str::startsWith(data_get($coordinates, 'public_id'), ['place', 'driver'])) {
218
+ return static::getPointFromMixed(data_get($coordinates, 'public_id'));
219
+ }
220
+ }
221
+
222
+ // if is a object/array model with location property
223
+ if (static::exists($coordinates, 'location')) {
224
+ return static::getPointFromMixed(data_get($coordinates, 'location'));
225
+ }
226
+
227
+ $latitude = static::or($coordinates, ['_lat', 'lat', '_latitude', 'latitude', 'x', '0']);
228
+ $longitude = static::or($coordinates, ['lon', '_lon', 'long', 'lng', '_lng', '_longitude', 'longitude', 'y', '1']);
229
+ }
230
+
231
+ if (is_string($coordinates)) {
232
+ $coords = [];
233
+
234
+ if (Str::startsWith($coordinates, 'place_')) {
235
+ $resolvedPlace = \Fleetbase\FleetOps\Models\Place::where('public_id', $coordinates)->first();
236
+ if ($resolvedPlace instanceof \Fleetbase\FleetOps\Models\Place) {
237
+ return static::getPointFromMixed($resolvedPlace);
238
+ }
239
+
240
+ return null;
241
+ }
242
+
243
+ if (Str::startsWith($coordinates, 'driver_')) {
244
+ $resolvedDriver = \Fleetbase\FleetOps\Models\Driver::where('public_id', $coordinates)->first();
245
+ if ($resolvedDriver instanceof \Fleetbase\FleetOps\Models\Driver) {
246
+ return static::getPointFromMixed($resolvedDriver);
247
+ }
248
+
249
+ return null;
250
+ }
251
+
252
+ if (Str::isUuid($coordinates)) {
253
+ $resolvedPlace = \Fleetbase\FleetOps\Models\Place::where('uuid', $coordinates)->first();
254
+ if ($resolvedPlace instanceof \Fleetbase\FleetOps\Models\Place) {
255
+ return static::getPointFromMixed($resolvedPlace);
256
+ }
257
+
258
+ $resolvedDriver = \Fleetbase\FleetOps\Models\Driver::where('uuid', $coordinates)->first();
259
+ if ($resolvedDriver instanceof \Fleetbase\FleetOps\Models\Driver) {
260
+ return static::getPointFromMixed($resolvedDriver);
261
+ }
262
+
263
+ return null;
264
+ }
265
+
266
+ if (Str::startsWith($coordinates, 'POINT(')) {
267
+ $coordinates = Str::replaceFirst('POINT(', '', $coordinates);
268
+ $coordinates = Str::replace(')', '', $coordinates);
269
+ $coords = explode(' ', $coordinates);
270
+ $coords = array_reverse($coords);
271
+ $coordinates = null;
272
+ }
273
+
274
+ if (preg_match('/LatLng\(([^,]+),\s*([^)]+)\)/', $coordinates, $matches)) {
275
+ $coords = [
276
+ floatval($matches[1]),
277
+ floatval($matches[2]),
278
+ ];
279
+
280
+ $coordinates = null;
281
+ }
282
+
283
+ if (Str::contains($coordinates, ',')) {
284
+ $coords = explode(',', $coordinates);
285
+ }
286
+
287
+ if (Str::contains($coordinates, '|')) {
288
+ $coords = explode('|', $coordinates);
289
+ }
290
+
291
+ if (Str::contains($coordinates, ' ')) {
292
+ $coords = explode(' ', $coordinates);
293
+ }
294
+
295
+ $latitude = $coords[0];
296
+ $longitude = $coords[1];
297
+ }
298
+
299
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
300
+ return $coordinates;
301
+ }
302
+
303
+ // if longitude and latitude is invalide throw exception
304
+ if ($latitude === null && $longitude === null) {
305
+ throw new \Exception('Attempted to resolve Point from invalid location.');
306
+ }
307
+
308
+ return new \Grimzy\LaravelMysqlSpatial\Types\Point((float) $latitude, (float) $longitude);
309
+ }
310
+
311
+ /**
312
+ * Determines if the given coordinates strictly represent a Point object.
313
+ * These will explude resolvable coordinates from records.
314
+ *
315
+ * @param mixed $coordinates the coordinates, which can be an array, object, or string
316
+ *
317
+ * @return bool returns true if $coordinates represents a Point object, false otherwise
318
+ */
319
+ public static function isCoordinatesStrict($coordinates)
320
+ {
321
+ return static::getPointFromCoordinatesStrict($coordinates) instanceof \Grimzy\LaravelMysqlSpatial\Types\Point;
322
+ }
323
+
324
+ /**
325
+ * Resolves a GeoJson/array/object or string representing a point to a Point object.
326
+ * These will explude resolvable coordinates from records.
327
+ *
328
+ * @param mixed $coordinates the coordinates, which can be an array, object, or string
329
+ *
330
+ * @return \Grimzy\LaravelMysqlSpatial\Types\Point Returns the Point instance
331
+ */
332
+ public static function getPointFromCoordinatesStrict($coordinates): ?\Grimzy\LaravelMysqlSpatial\Types\Point
333
+ {
334
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression) {
335
+ $coordinates = $coordinates->getSpatialValue();
336
+ }
337
+
338
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
339
+ return $coordinates;
340
+ }
341
+
342
+ if (static::isGeoJson($coordinates)) {
343
+ $coordinatesJson = null;
344
+
345
+ if (is_array($coordinates) || is_object($coordinates)) {
346
+ $coordinatesJson = json_encode($coordinates);
347
+ }
348
+
349
+ if (static::isJson($coordinatesJson)) {
350
+ $coordinates = \Grimzy\LaravelMysqlSpatial\Types\Point::fromJson($coordinatesJson);
351
+ }
352
+ }
353
+
354
+ if (is_array($coordinates) || is_object($coordinates)) {
355
+ $latitude = static::or($coordinates, ['_lat', 'lat', '_latitude', 'latitude', 'x', '0']);
356
+ $longitude = static::or($coordinates, ['lon', '_lon', 'long', 'lng', '_lng', '_longitude', 'longitude', 'y', '1']);
357
+
358
+ if (is_numeric($latitude) && is_numeric($longitude)) {
359
+ $coordinates = new \Grimzy\LaravelMysqlSpatial\Types\Point((float) $latitude, (float) $longitude);
360
+ }
361
+ }
362
+
363
+ if (is_string($coordinates)) {
364
+ $coords = [];
365
+
366
+ if (Str::startsWith($coordinates, 'POINT(')) {
367
+ $coordinates = Str::replaceFirst('POINT(', '', $coordinates);
368
+ $coordinates = Str::replace(')', '', $coordinates);
369
+ $coords = explode(' ', $coordinates);
370
+ $coords = array_reverse($coords);
371
+ $coordinates = null;
372
+ }
373
+
374
+ if (preg_match('/LatLng\(([^,]+),\s*([^)]+)\)/', $coordinates, $matches)) {
375
+ $coords = [
376
+ floatval($matches[1]),
377
+ floatval($matches[2]),
378
+ ];
379
+
380
+ $coordinates = null;
381
+ }
382
+
383
+ if (Str::contains($coordinates, ',')) {
384
+ $coords = explode(',', $coordinates);
385
+ }
386
+
387
+ if (Str::contains($coordinates, '|')) {
388
+ $coords = explode('|', $coordinates);
389
+ }
390
+
391
+ if (Str::contains($coordinates, ' ')) {
392
+ $coords = explode(' ', $coordinates);
393
+ }
394
+
395
+ $latitude = $coords[0];
396
+ $longitude = $coords[1];
397
+
398
+ if (is_numeric($latitude) && is_numeric($longitude)) {
399
+ $coordinates = new \Grimzy\LaravelMysqlSpatial\Types\Point((float) $latitude, (float) $longitude);
400
+ }
401
+ }
402
+
403
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
404
+ return $coordinates;
405
+ }
406
+
407
+ return null;
408
+ }
409
+
410
+ /**
411
+ * Gets a coordinate property from coordinates.
412
+ *
413
+ * @param mixed $coordinates the coordinates input value to extract a coordinate from
414
+ * @param string $prop the coordinate property to extract ('latitude' or 'longitude')
415
+ *
416
+ * @return float the extracted coordinate value as a float
417
+ */
418
+ public static function getCoordinateFromCoordinates($coordinates, $prop = 'latitude'): float
419
+ {
420
+ $latitude = null;
421
+ $longitude = null;
422
+
423
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression) {
424
+ $coordinates = $coordinates->getSpatialValue();
425
+ }
426
+
427
+ if ($coordinates instanceof \Fleetbase\FleetOps\Models\Place) {
428
+ $coordinates = $coordinates->location;
429
+ }
430
+
431
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
432
+ $latitude = $coordinates->getLat();
433
+ $longitude = $coordinates->getLng();
434
+ } elseif (is_array($coordinates) || is_object($coordinates)) {
435
+ $latitude = static::or($coordinates, ['_lat', 'lat', '_latitude', 'latitude', 'x', '0']);
436
+ $longitude = static::or($coordinates, ['lon', '_lon', 'long', 'lng', '_lng', '_longitude', 'longitude', 'y', '1']);
437
+ }
438
+
439
+ if (is_string($coordinates)) {
440
+ $coords = [];
441
+
442
+ if (Str::startsWith($coordinates, 'place_')) {
443
+ $resolvedPlace = \Fleetbase\FleetOps\Models\Place::where('public_id', $coordinates)->first();
444
+ if ($resolvedPlace instanceof \Fleetbase\FleetOps\Models\Place) {
445
+ return static::getCoordinateFromCoordinates($resolvedPlace);
446
+ }
447
+ }
448
+
449
+ if (Str::isUuid($coordinates)) {
450
+ $resolvedPlace = \Fleetbase\FleetOps\Models\Place::where('uuid', $coordinates)->first();
451
+ if ($resolvedPlace instanceof \Fleetbase\FleetOps\Models\Place) {
452
+ return static::getCoordinateFromCoordinates($resolvedPlace);
453
+ }
454
+ }
455
+
456
+ if (Str::startsWith($coordinates, 'POINT(')) {
457
+ $coordinates = Str::replaceFirst('POINT(', '', $coordinates);
458
+ $coordinates = Str::replace(')', '', $coordinates);
459
+ $coords = explode(' ', $coordinates);
460
+ $coords = array_reverse($coords);
461
+ $coordinates = null;
462
+ }
463
+
464
+ if (preg_match('/LatLng\(([^,]+),\s*([^)]+)\)/', $coordinates, $matches)) {
465
+ $coords = [
466
+ floatval($matches[1]),
467
+ floatval($matches[2]),
468
+ ];
469
+
470
+ $coordinates = null;
471
+ }
472
+
473
+ if (Str::contains($coordinates, ',')) {
474
+ $coords = explode(',', $coordinates);
475
+ }
476
+
477
+ if (Str::contains($coordinates, '|')) {
478
+ $coords = explode('|', $coordinates);
479
+ }
480
+
481
+ if (Str::contains($coordinates, ' ')) {
482
+ $coords = explode(' ', $coordinates);
483
+ }
484
+
485
+ $latitude = $coords[0];
486
+ $longitude = $coords[1];
487
+ }
488
+
489
+ return $prop === 'latitude' ? (float) $latitude : (float) $longitude;
490
+ }
491
+
492
+ /**
493
+ * Extracts the latitude coordinate value from a given input value representing a location or set of coordinates.
494
+ *
495
+ * @param mixed $coordinates the input value representing a location or set of coordinates
496
+ *
497
+ * @return float the extracted latitude coordinate value as a float
498
+ */
499
+ public static function getLatitudeFromCoordinates($coordinates): float
500
+ {
501
+ return static::getCoordinateFromCoordinates($coordinates);
502
+ }
503
+
504
+ /**
505
+ * Extracts the longitude coordinate value from a given input value representing a location or set of coordinates.
506
+ *
507
+ * @param mixed $coordinates the input value representing a location or set of coordinates
508
+ *
509
+ * @return float the extracted longitude coordinate value as a float
510
+ */
511
+ public static function getLongitudeFromCoordinates($coordinates): float
512
+ {
513
+ return static::getCoordinateFromCoordinates($coordinates, 'longitude');
514
+ }
515
+
516
+ /**
517
+ * Extracts a point value from a given input value representing a location or set of coordinates.
518
+ *
519
+ * @param mixed $coordinates the input value representing a location or set of coordinates
520
+ *
521
+ * @return \Grimzy\LaravelMysqlSpatial\Types\Point the extracted point value
522
+ */
523
+ public static function getPointFromCoordinates($coordinates): \Grimzy\LaravelMysqlSpatial\Types\Point
524
+ {
525
+ if ($coordinates instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
526
+ return $coordinates;
527
+ }
528
+
529
+ if (is_null($coordinates) || !static::isCoordinates($coordinates)) {
530
+ return new \Grimzy\LaravelMysqlSpatial\Types\Point(0, 0);
531
+ }
532
+
533
+ $latitude = static::getLatitudeFromCoordinates($coordinates);
534
+ $longitude = static::getLongitudeFromCoordinates($coordinates);
535
+
536
+ return new \Grimzy\LaravelMysqlSpatial\Types\Point($latitude, $longitude);
537
+ }
538
+
539
+ /**
540
+ * Converts a point to a WKT (Well-Known Text) representation for SQL insert.
541
+ *
542
+ * @param mixed $point the input value representing a point
543
+ *
544
+ * @return \Illuminate\Database\Query\Expression the WKT representation of the point as a raw SQL expression
545
+ */
546
+ public static function parsePointToWkt($point): \Illuminate\Database\Query\Expression
547
+ {
548
+ $wkt = 'POINT(0 0)';
549
+
550
+ if ($point instanceof \Grimzy\LaravelMysqlSpatial\Types\Point) {
551
+ $wkt = $point->toWKT();
552
+ }
553
+
554
+ if (is_array($point)) {
555
+ $json = json_encode($point);
556
+ $p = \Grimzy\LaravelMysqlSpatial\Types\Point::fromJson($json);
557
+
558
+ $wkt = $p->toWkt();
559
+ }
560
+
561
+ if (is_string($point)) {
562
+ $p = \Grimzy\LaravelMysqlSpatial\Types\Point::fromString($point);
563
+
564
+ $wkt = $p->toWKT();
565
+ }
566
+
567
+ // Use the `ST_PointFromText` function to convert the WKT representation to a SQL expression.
568
+ return DB::raw("(ST_PointFromText('$wkt', 0, 'axis-order=long-lat'))");
569
+ }
570
+
571
+ /**
572
+ * Converts a raw point binary string to a float pair representing the point's coordinates.
573
+ *
574
+ * @param string $data the binary string representing the point
575
+ *
576
+ * @return array the float pair representing the point's coordinates
577
+ */
578
+ public static function rawPointToFloatPair(string $data): array
579
+ {
580
+ // Use the `unpack` function to extract the X and Y coordinates from the binary string.
581
+ $res = unpack('lSRID/CByteOrder/lTypeInfo/dX/dY', $data);
582
+
583
+ // Return the float pair representing the point's coordinates.
584
+ return [$res['X'], $res['Y']];
585
+ }
586
+
587
+ /**
588
+ * Converts a raw point binary string to a Laravel MySQL Spatial Point instance.
589
+ *
590
+ * @param string $data the binary string representing the point
591
+ *
592
+ * @return \Grimzy\LaravelMysqlSpatial\Types\Point the Laravel MySQL Spatial Point instance
593
+ */
594
+ public static function rawPointToPoint(string $data): \Grimzy\LaravelMysqlSpatial\Types\Point
595
+ {
596
+ // Use the `unpack` function to extract the X, Y, and SRID values from the binary string.
597
+ $res = unpack('lSRID/CByteOrder/lTypeInfo/dX/dY', $data);
598
+
599
+ // Return a new Laravel MySQL Spatial Point instance with the X, Y, and SRID values.
600
+ return new \Grimzy\LaravelMysqlSpatial\Types\Point($res['X'], $res['Y'], $res['SRID']);
601
+ }
602
+
603
+ /**
604
+ * Calculates driving distance and time using Google distance matrix.
605
+ * Returns distance in meters and time in seconds.
606
+ *
607
+ * @param \Fleetbase\FleetOps\Models\Place|\Grimzy\LaravelMysqlSpatial\Types\Point|array $origin
608
+ * @param \Fleetbase\FleetOps\Models\Place|\Grimzy\LaravelMysqlSpatial\Types\Point|array $destination
609
+ */
610
+ public static function getDrivingDistanceAndTime($origin, $destination): DistanceMatrix
611
+ {
612
+ if ($origin instanceof \Fleetbase\FleetOps\Models\Place) {
613
+ $origin = static::createObject(
614
+ [
615
+ 'latitude' => $origin->location->getLat(),
616
+ 'longitude' => $origin->location->getLng(),
617
+ ]
618
+ );
619
+ } else {
620
+ $point = static::getPointFromMixed($origin);
621
+ $origin = static::createObject(
622
+ [
623
+ 'latitude' => $point->getLat(),
624
+ 'longitude' => $point->getLng(),
625
+ ]
626
+ );
627
+ }
628
+
629
+ if ($destination instanceof \Fleetbase\FleetOps\Models\Place) {
630
+ $destination = static::createObject(
631
+ [
632
+ 'latitude' => $destination->location->getLat(),
633
+ 'longitude' => $destination->location->getLng(),
634
+ ]
635
+ );
636
+ } else {
637
+ $point = static::getPointFromMixed($destination);
638
+ $destination = static::createObject(
639
+ [
640
+ 'latitude' => $point->getLat(),
641
+ 'longitude' => $point->getLng(),
642
+ ]
643
+ );
644
+ }
645
+
646
+ $cacheKey = $origin->latitude . ':' . $origin->longitude . ':' . $destination->latitude . ':' . $destination->longitude;
647
+
648
+ // check cache for results
649
+ $cachedResult = Redis::get($cacheKey);
650
+
651
+ if ($cachedResult) {
652
+ $json = json_decode($cachedResult);
653
+
654
+ if (!empty($json->distance) && !empty($json->time)) {
655
+ return new DistanceMatrix($json->distance, $json->time);
656
+ }
657
+ }
658
+
659
+ $response = Http::get(
660
+ 'https://maps.googleapis.com/maps/api/distancematrix/json',
661
+ [
662
+ 'origins' => $origin->latitude . ',' . $origin->longitude,
663
+ 'destinations' => $destination->latitude . ',' . $destination->longitude,
664
+ 'mode' => 'driving',
665
+ 'key' => env('GOOGLE_MAPS_API_KEY'),
666
+ ]
667
+ )->json();
668
+
669
+ $distance = data_get($response, 'rows.0.elements.0.distance.value');
670
+ $time = data_get($response, 'rows.0.elements.0.duration.value');
671
+
672
+ $result = static::createObject(
673
+ [
674
+ 'distance' => $distance,
675
+ 'time' => $time,
676
+ ]
677
+ );
678
+
679
+ // cache result
680
+ Redis::set($cacheKey, json_encode($result));
681
+
682
+ return new DistanceMatrix($distance, $time);
683
+ }
684
+
685
+ /**
686
+ * Calculates driving distance and time using Google distance matrix for multiple origins or destinations.
687
+ * Returns distance in meters and time in seconds.
688
+ *
689
+ * @param Place|Point|array $origins
690
+ * @param Place|Point|array $destinations
691
+ */
692
+ public static function distanceMatrix($origins = [], $destinations = []): DistanceMatrix
693
+ {
694
+ if ($origins instanceof \Illuminate\Support\Collection) {
695
+ $origins = $origins->all();
696
+ }
697
+
698
+ if ($destinations instanceof \Illuminate\Support\Collection) {
699
+ $destinations = $destinations->all();
700
+ }
701
+
702
+ $origins = array_map(
703
+ function ($origin) {
704
+ $point = static::getPointFromMixed($origin);
705
+ $origin = static::createObject(
706
+ [
707
+ 'latitude' => $point->getLat(),
708
+ 'longitude' => $point->getLng(),
709
+ ]
710
+ );
711
+
712
+ return $origin;
713
+ },
714
+ $origins
715
+ );
716
+
717
+ $destinations = array_map(
718
+ function ($destination) {
719
+ $point = static::getPointFromMixed($destination);
720
+ $destination = static::createObject(
721
+ [
722
+ 'latitude' => $point->getLat(),
723
+ 'longitude' => $point->getLng(),
724
+ ]
725
+ );
726
+
727
+ return $destination;
728
+ },
729
+ $destinations
730
+ );
731
+
732
+ // get url ready string for origins
733
+ $originsString = implode('|', array_map(
734
+ function ($origin) {
735
+ return $origin->latitude . ',' . $origin->longitude;
736
+ },
737
+ $origins
738
+ ));
739
+
740
+ // get url ready string for origins
741
+ $destinationString = implode('|', array_map(
742
+ function ($destination) {
743
+ return $destination->latitude . ',' . $destination->longitude;
744
+ },
745
+ $destinations
746
+ ));
747
+
748
+ $cacheKey = md5($originsString . '_' . $destinationString);
749
+
750
+ // check cache for results
751
+ $cachedResult = Redis::get($cacheKey);
752
+
753
+ if ($cachedResult) {
754
+ $json = json_decode($cachedResult);
755
+
756
+ if (!empty($json->distance) && !empty($json->time)) {
757
+ return new DistanceMatrix($json->distance, $json->time);
758
+ }
759
+ }
760
+
761
+ $response = Http::get(
762
+ 'https://maps.googleapis.com/maps/api/distancematrix/json',
763
+ [
764
+ 'origins' => $originsString,
765
+ 'destinations' => $destinationString,
766
+ 'mode' => 'driving',
767
+ 'key' => env('GOOGLE_MAPS_API_KEY'),
768
+ ]
769
+ )->json();
770
+
771
+ $distance = data_get($response, 'rows.0.elements.0.distance.value');
772
+ $time = data_get($response, 'rows.0.elements.0.duration.value');
773
+
774
+ $result = static::createObject(
775
+ [
776
+ 'distance' => $distance,
777
+ 'time' => $time,
778
+ ]
779
+ );
780
+
781
+ // cache result
782
+ Redis::set($cacheKey, json_encode($result));
783
+
784
+ return new DistanceMatrix($distance, $time);
785
+ }
786
+
787
+ /**
788
+ * Calculates driving distance and time between two points using Vincenty's formula.
789
+ * Returns distance in meters and time in seconds.
790
+ *
791
+ * @param \Fleetbase\FleetOps\Models\Place|\Grimzy\LaravelMysqlSpatial\Types\Point|array $origin
792
+ * @param \Fleetbase\FleetOps\Models\Place|\Grimzy\LaravelMysqlSpatial\Types\Point|array $destination
793
+ */
794
+ public static function calculateDrivingDistanceAndTime($origin, $destination): DistanceMatrix
795
+ {
796
+ $origin = static::getPointFromMixed($origin);
797
+ $destination = static::getPointFromMixed($destination);
798
+
799
+ $distance = Utils::vincentyGreatCircleDistance($origin, $destination);
800
+ $time = round($distance / 100) * self::DRIVING_TIME_MULTIPLIER;
801
+
802
+ return new DistanceMatrix($distance, $time);
803
+ }
804
+
805
+ /**
806
+ * Alias for `calculateDrivingDistanceAndTime`
807
+ * Calculates driving distance and time between two points using Vincenty's formula.
808
+ * Returns distance in meters and time in seconds.
809
+ *
810
+ * @param \Fleetbase\FleetOps\Models\Place|\Grimzy\LaravelMysqlSpatial\Types\Point|array $origin
811
+ * @param \Fleetbase\FleetOps\Models\Place|\Grimzy\LaravelMysqlSpatial\Types\Point|array $destination
812
+ */
813
+ public static function getPreliminaryDistanceMatrix($origin, $destination): DistanceMatrix
814
+ {
815
+ return static::calculateDrivingDistanceAndTime($origin, $destination);
816
+ }
817
+
818
+ /**
819
+ * Format distance in meters to kilometers or meters.
820
+ *
821
+ * @param float $meters Distance in meters
822
+ * @param bool $abbreviate Whether to use abbreviated unit or not
823
+ */
824
+ public static function formatMeters(float $meters, bool $abbreviate = true): string
825
+ {
826
+ if ($meters > 1000) {
827
+ $distance = round($meters / 1000, 2);
828
+ $unit = $abbreviate ? 'km' : 'kilometers';
829
+ } else {
830
+ $distance = round($meters);
831
+ $unit = $abbreviate ? 'm' : 'meters';
832
+ }
833
+
834
+ return $distance . ' ' . $unit;
835
+ }
836
+
837
+ /**
838
+ * Calculates the great-circle distance between two points, with
839
+ * the Vincenty formula. (Using over haversine tdue to antipodal point issues).
840
+ *
841
+ * https://en.wikipedia.org/wiki/Great-circle_distance#Formulas
842
+ * https://en.wikipedia.org/wiki/Antipodal_point
843
+ *
844
+ * @param \Grimzy\LaravelMysqlSpatial\Types\Point Starting point
845
+ * @param \Grimzy\LaravelMysqlSpatial\Types\Point Ending point
846
+ * @param float $earthRadius Mean earth radius in [m]
847
+ *
848
+ * @return float Distance between points in [m] (same as earthRadius)
849
+ */
850
+ public static function vincentyGreatCircleDistance(\Grimzy\LaravelMysqlSpatial\Types\Point $from, \Grimzy\LaravelMysqlSpatial\Types\Point $to, float $earthRadius = 6371000): float
851
+ {
852
+ // convert from degrees to radians
853
+ $latFrom = deg2rad($from->getLat());
854
+ $lonFrom = deg2rad($from->getLng());
855
+ $latTo = deg2rad($to->getLat());
856
+ $lonTo = deg2rad($to->getLng());
857
+
858
+ $lonDelta = $lonTo - $lonFrom;
859
+ $a = pow(cos($latTo) * sin($lonDelta), 2) +
860
+ pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
861
+ $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);
862
+
863
+ $angle = atan2(sqrt($a), $b);
864
+
865
+ return $angle * $earthRadius;
866
+ }
867
+
868
+ /**
869
+ * Finds the nearest timezone for a given coordinate point.
870
+ */
871
+ public static function getNearestTimezone(\Grimzy\LaravelMysqlSpatial\Types\Point $location, string $countryCode = ''): string
872
+ {
873
+ $timezoneIds = \DateTimeZone::listIdentifiers(\DateTimeZone::PER_COUNTRY, $countryCode);
874
+ $lat = $location->getLat();
875
+ $long = $location->getLng();
876
+ $minDistance = INF;
877
+ $timezone = 'unknown';
878
+
879
+ foreach ($timezoneIds as $timezoneId) {
880
+ $tz = new \DateTimeZone($timezoneId);
881
+ $location = $tz->getLocation();
882
+ $tzLat = $location['latitude'];
883
+ $tzLong = $location['longitude'];
884
+
885
+ $theta = $long - $tzLong;
886
+ $distance = sin(deg2rad($lat)) * sin(deg2rad($tzLat)) + cos(deg2rad($lat)) * cos(deg2rad($tzLat)) * cos(deg2rad($theta));
887
+ $distance = acos($distance);
888
+ $distance = abs(rad2deg($distance));
889
+
890
+ if ($distance < $minDistance) {
891
+ $minDistance = $distance;
892
+ $timezone = $timezoneId;
893
+ }
894
+ }
895
+
896
+ return $timezone;
897
+ }
898
+
899
+ /**
900
+ * Determines whether a given vendor ID is an integrated vendor ID or not.
901
+ *
902
+ * @param string $id the vendor ID to check
903
+ *
904
+ * @return bool returns true if the given ID is an integrated vendor ID or is present in the "integrated_vendors" database table for the current company session, false otherwise
905
+ */
906
+ public static function isIntegratedVendorId(string $id): bool
907
+ {
908
+ if (Str::startsWith($id, 'integrated_vendor_')) {
909
+ return true;
910
+ }
911
+
912
+ $providerIds = DB::table('integrated_vendors')
913
+ ->select('provider')
914
+ ->where('company_uuid', session('company'))
915
+ ->distinct()
916
+ ->get()
917
+ ->map(function ($result) {
918
+ return $result->provider;
919
+ })
920
+ ->toArray();
921
+
922
+ return in_array($id, $providerIds);
923
+ }
924
+
925
+ /**
926
+ * Gets data from a JSON file containing globe data and decodes it into a PHP object.
927
+ *
928
+ * @return object the decoded JSON object containing globe data
929
+ */
930
+ public static function getGlobeData()
931
+ {
932
+ ini_set('memory_limit', '-1');
933
+
934
+ $data = file_get_contents(resource_path('data/globe.json'));
935
+ $geoJson = json_decode($data);
936
+
937
+ return $geoJson;
938
+ }
939
+
940
+ /**
941
+ * Creates a MultiPolygon object from the provided country's ISO code.
942
+ *
943
+ * @param string $country the ISO_A3 or ISO_A2 code of the country
944
+ *
945
+ * @return \Grimzy\LaravelMysqlSpatial\Types\MultiPolygon|null the MultiPolygon object or null if not found
946
+ */
947
+ public static function createPolygonFromCountry(string $country): ?\Grimzy\LaravelMysqlSpatial\Types\MultiPolygon
948
+ {
949
+ $globe = static::getGlobeData();
950
+ $country = strtolower($country);
951
+
952
+ $feature = collect($globe->features)->first(
953
+ function ($feature) use ($country) {
954
+ if (!isset($feature->properties->ISO_A3) || !isset($feature->properties->ISO_A2)) {
955
+ return false;
956
+ }
957
+
958
+ return strtolower($feature->properties->ISO_A3) === $country || strtolower($feature->properties->ISO_A2) === $country;
959
+ }
960
+ );
961
+
962
+ if ($feature) {
963
+ return \Grimzy\LaravelMysqlSpatial\Types\MultiPolygon::fromJson(json_encode($feature->geometry));
964
+ }
965
+
966
+ return null;
967
+ }
968
+
969
+ public static function isPoint($point): bool
970
+ {
971
+ return $point instanceof \Grimzy\LaravelMysqlSpatial\Types\Point;
972
+ }
973
+
974
+ /**
975
+ * Converts latitude, longitude, and radius to an array of circle coordinates.
976
+ *
977
+ * @param float $latitude the latitude in degrees
978
+ * @param float $longitude the longitude in degrees
979
+ * @param float $meters the radius in meters
980
+ *
981
+ * @return array an array of circle coordinates
982
+ */
983
+ public static function coordsToCircle($latitude, $longitude, $meters)
984
+ {
985
+ $latitude = deg2rad($latitude);
986
+ $longitude = deg2rad($longitude);
987
+ // convert meters to km
988
+ $radius = ($meters * 1000) / 6378137;
989
+ // create circle coordinates
990
+ $coords = collect();
991
+ // loop through the array and write path linestrings
992
+ for ($i = 0; $i <= 360; $i += 3) {
993
+ $radial = deg2rad($i);
994
+ $lat_rad = asin(sin($latitude) * cos($radius) + cos($latitude) * sin($radius) * cos($radial));
995
+ $dlon_rad = atan2(sin($radial) * sin($radius) * cos($latitude), cos($radius) - sin($latitude) * sin($lat_rad));
996
+ $lon_rad = fmod($longitude + $dlon_rad + M_PI, 2 * M_PI) - M_PI;
997
+ $coords->push([rad2deg($lat_rad), rad2deg($lon_rad)]);
998
+ }
999
+
1000
+ return $coords->toArray();
1001
+ }
1002
+
1003
+ /**
1004
+ * Calculates the centroid (geometric center) of the provided coordinates.
1005
+ *
1006
+ * @param array $coord an array of coordinates
1007
+ *
1008
+ * @return array the centroid of the coordinates as an array [latitude, longitude]
1009
+ */
1010
+ public static function getCentroid($coord)
1011
+ {
1012
+ $centroid = array_reduce($coord, function ($x, $y) use ($coord) {
1013
+ $len = count($coord);
1014
+
1015
+ return [$x[0] + $y[0] / $len, $x[1] + $y[1] / $len];
1016
+ }, [0, 0]);
1017
+
1018
+ return $centroid;
1019
+ }
1020
+
1021
+ public static function getCoordinatesFromPolygon(?\Grimzy\LaravelMysqlSpatial\Types\Polygon $polygon): array
1022
+ {
1023
+ return Arr::first($polygon->jsonSerialize()->getCoordinates());
1024
+ }
1025
+
1026
+ /**
1027
+ * Alias function to `getModelClassName` but uses FleetOps namespace.
1028
+ *
1029
+ * @param string|object $table the table name or an object instance to derive the class name from
1030
+ * @param string|array $namespaceSegments a string representing the namespace or an array of segments to be appended to the model class name
1031
+ *
1032
+ * @return string the fully qualified class name, including the namespace
1033
+ *
1034
+ * @throws InvalidArgumentException if the provided $namespaceSegments is not a string or an array
1035
+ */
1036
+ public static function getModelClassName($table, $namespaceSegments = '\\Fleetbase\\FleetOps\\'): string
1037
+ {
1038
+ return parent::getModelClassName($table, $namespaceSegments);
1039
+ }
1040
+
1041
+ /**
1042
+ * Determines if the given variable is a valid GeoJSON.
1043
+ *
1044
+ * This function accepts a GeoJSON string, array, or object and checks for the required
1045
+ * GeoJSON properties: 'type' and either 'coordinates' or 'geometries', depending on the type.
1046
+ *
1047
+ * @param string|array|object $geoJson the GeoJSON input, which can be a JSON string, an array, or an object
1048
+ *
1049
+ * @return bool returns true if the input is valid GeoJSON, false otherwise
1050
+ *
1051
+ * @example
1052
+ * $geoJsonString = '{"type":"Point","coordinates":[106.9338169,47.9131423]}';
1053
+ * isGeoJson($geoJsonString); // true
1054
+ */
1055
+ public static function isGeoJson($geoJson)
1056
+ {
1057
+ // If the input is a JSON string, decode it
1058
+ if (is_string($geoJson) && static::isJson($geoJson)) {
1059
+ $geoJson = json_decode($geoJson, true);
1060
+ }
1061
+
1062
+ // Check if the input is an array or an object
1063
+ if (is_array($geoJson) || is_object($geoJson)) {
1064
+ // Convert to array if it's an object
1065
+ if (is_object($geoJson)) {
1066
+ $geoJson = (array) $geoJson;
1067
+ }
1068
+
1069
+ // Check for required GeoJSON properties: 'type' and 'coordinates' or 'geometries'
1070
+ if (isset($geoJson['type'])) {
1071
+ if ($geoJson['type'] === 'GeometryCollection' && isset($geoJson['geometries'])) {
1072
+ return true;
1073
+ } elseif (in_array($geoJson['type'], ['Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon']) && isset($geoJson['coordinates'])) {
1074
+ return true;
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ return false;
1080
+ }
1081
+
1082
+ /**
1083
+ * Creates a SpatialExpression object from a valid GeoJSON input.
1084
+ *
1085
+ * @param mixed $geoJson the GeoJSON input, which can be an array, object, or JSON string representing a valid GeoJSON object
1086
+ *
1087
+ * @return \Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression|null returns a SpatialExpression object if the input is a valid GeoJSON, or null if the input is not valid
1088
+ *
1089
+ * @throws \InvalidArgumentException if the input is not a valid GeoJSON object
1090
+ */
1091
+ public static function createSpatialExpressionFromGeoJson($geoJson): ?\Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression
1092
+ {
1093
+ if (!static::isGeoJson($geoJson)) {
1094
+ return null;
1095
+ }
1096
+
1097
+ if (is_string($geoJson) && static::isJson($geoJson)) {
1098
+ $geoJson = json_decode($geoJson, true);
1099
+ }
1100
+
1101
+ // Convert the value to JSON and create a Geometry object
1102
+ $json = json_encode($geoJson);
1103
+ $geo = \Grimzy\LaravelMysqlSpatial\Types\Geometry::fromJson($json);
1104
+
1105
+ // Return a new SpatialExpression object
1106
+ return new \Grimzy\LaravelMysqlSpatial\Eloquent\SpatialExpression($geo);
1107
+ }
1108
+
1109
+ /**
1110
+ * Calculate the heading between two geographical points.
1111
+ *
1112
+ * @param Point $point1 the starting point with latitude and longitude
1113
+ * @param Point $point2 the ending point with latitude and longitude
1114
+ *
1115
+ * @return float the heading in degrees from the starting point to the ending point
1116
+ */
1117
+ public static function calculateHeading(\Grimzy\LaravelMysqlSpatial\Types\Point $point1, \Grimzy\LaravelMysqlSpatial\Types\Point $point2): float
1118
+ {
1119
+ // Extract latitude and longitude from Point objects
1120
+ $lat1 = $point1->getLat();
1121
+ $lon1 = $point1->getLng();
1122
+ $lat2 = $point2->getLat();
1123
+ $lon2 = $point2->getLng();
1124
+
1125
+ // Convert latitude and longitude from degrees to radians
1126
+ $lat1 = deg2rad($lat1);
1127
+ $lat2 = deg2rad($lat2);
1128
+ $delta_lon = deg2rad($lon2 - $lon1);
1129
+
1130
+ // Calculate X and Y differences
1131
+ $x = cos($lat2) * sin($delta_lon);
1132
+ $y = cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($delta_lon);
1133
+
1134
+ // Calculate the angle
1135
+ $theta_rad = atan2($x, $y);
1136
+
1137
+ // Convert the angle from radians to degrees
1138
+ $theta_deg = rad2deg($theta_rad);
1139
+
1140
+ // Normalize the degree to be between 0 and 360
1141
+ $theta_deg = fmod($theta_deg + 360, 360);
1142
+
1143
+ return $theta_deg;
1144
+ }
1145
+ }