@aggdirect/coolmap-services 1.4.8 → 4.0.0
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 +43 -18
- package/fesm2022/coolmap-services.mjs +1339 -0
- package/fesm2022/coolmap-services.mjs.map +1 -0
- package/index.d.ts +212 -5
- package/package.json +12 -29
- package/esm2022/aggdirect-coolmap-services.mjs +0 -5
- package/esm2022/lib/service/coolmap.service.mjs +0 -633
- package/esm2022/lib/service/data-model.mjs +0 -92
- package/esm2022/lib/service/utils.service.mjs +0 -400
- package/esm2022/public-api.mjs +0 -7
- package/fesm2022/aggdirect-coolmap-services.mjs +0 -1130
- package/fesm2022/aggdirect-coolmap-services.mjs.map +0 -1
- package/lib/service/coolmap.service.d.ts +0 -47
- package/lib/service/data-model.d.ts +0 -94
- package/lib/service/utils.service.d.ts +0 -78
- package/public-api.d.ts +0 -3
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Component, InjectionToken, inject, NgZone, Injectable, signal } from '@angular/core';
|
|
3
|
+
import { BehaviorSubject } from 'rxjs';
|
|
4
|
+
import mapboxgl from 'mapbox-gl';
|
|
5
|
+
import * as turf from '@turf/turf';
|
|
6
|
+
import { HttpClient } from '@angular/common/http';
|
|
7
|
+
|
|
8
|
+
class CoolmapServices {
|
|
9
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapServices, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: CoolmapServices, isStandalone: true, selector: "lib-coolmap-services", ngImport: i0, template: `
|
|
11
|
+
<p>
|
|
12
|
+
coolmap-services works!
|
|
13
|
+
</p>
|
|
14
|
+
`, isInline: true, styles: [""] });
|
|
15
|
+
}
|
|
16
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapServices, decorators: [{
|
|
17
|
+
type: Component,
|
|
18
|
+
args: [{ selector: 'lib-coolmap-services', imports: [], template: `
|
|
19
|
+
<p>
|
|
20
|
+
coolmap-services works!
|
|
21
|
+
</p>
|
|
22
|
+
` }]
|
|
23
|
+
}] });
|
|
24
|
+
|
|
25
|
+
const COOLMAP_CONFIG = new InjectionToken('COOLMAP_CONFIG');
|
|
26
|
+
function provideCoolmapConfig(config) {
|
|
27
|
+
return {
|
|
28
|
+
provide: COOLMAP_CONFIG,
|
|
29
|
+
useValue: config
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class UtilsService {
|
|
34
|
+
http = inject(HttpClient);
|
|
35
|
+
config = inject(COOLMAP_CONFIG);
|
|
36
|
+
ngZone = inject(NgZone);
|
|
37
|
+
// BehaviorSubjects for state (converting to simple Signals later where practical)
|
|
38
|
+
preventnavChange = new BehaviorSubject(false);
|
|
39
|
+
navChangeObserve = this.preventnavChange.asObservable();
|
|
40
|
+
clearViewRouteforJobCode = new BehaviorSubject(false);
|
|
41
|
+
clearViewRouteforJobCodeObserve = this.clearViewRouteforJobCode.asObservable();
|
|
42
|
+
preVentJobdetailclose = new BehaviorSubject(false);
|
|
43
|
+
getpreVentJobdetailclose = this.preVentJobdetailclose.asObservable();
|
|
44
|
+
routeDetailsUtility = new BehaviorSubject({});
|
|
45
|
+
getrouteDetailsUtility = this.routeDetailsUtility.asObservable();
|
|
46
|
+
removeMapEntity = new BehaviorSubject({});
|
|
47
|
+
removeMapEntityUtility = this.removeMapEntity.asObservable();
|
|
48
|
+
dict = new Map();
|
|
49
|
+
unitsList;
|
|
50
|
+
materialsList;
|
|
51
|
+
materialsListForCustomer;
|
|
52
|
+
customersList;
|
|
53
|
+
locationList;
|
|
54
|
+
// Options filtering properties
|
|
55
|
+
pickupOptions = [];
|
|
56
|
+
destOptions = [];
|
|
57
|
+
ownerOptions = [];
|
|
58
|
+
customerOptions = [];
|
|
59
|
+
unitOptions = [];
|
|
60
|
+
materialOptions = [];
|
|
61
|
+
jcodeOptions = [];
|
|
62
|
+
driverOption = [];
|
|
63
|
+
truckingCompanayOption = [];
|
|
64
|
+
routeNameOptions = [];
|
|
65
|
+
getDateFormat(strVal, seprater) {
|
|
66
|
+
seprater = seprater ? seprater : '-';
|
|
67
|
+
const mydate = strVal;
|
|
68
|
+
if (!mydate)
|
|
69
|
+
return '';
|
|
70
|
+
const month = String(mydate.getMonth() + 1).padStart(2, '0');
|
|
71
|
+
const day = String(mydate.getDate()).padStart(2, '0');
|
|
72
|
+
return (mydate.getFullYear() +
|
|
73
|
+
seprater +
|
|
74
|
+
month +
|
|
75
|
+
seprater +
|
|
76
|
+
day);
|
|
77
|
+
}
|
|
78
|
+
getData(path) {
|
|
79
|
+
return this.http.get(`${this.config.analyticsRESTURL}${this.config.repository}/${path}`);
|
|
80
|
+
}
|
|
81
|
+
getRestData(path) {
|
|
82
|
+
return this.http.get(`${this.config.RESTURLPrefix}${path}`);
|
|
83
|
+
}
|
|
84
|
+
postdata(path, data) {
|
|
85
|
+
return this.http.post(`${this.config.analyticsRESTURL}${this.config.repository}/${path}`, data);
|
|
86
|
+
}
|
|
87
|
+
postDataWithRestUrl(path, data) {
|
|
88
|
+
return this.http.post(`${this.config.RESTURLPrefix}${path}`, data);
|
|
89
|
+
}
|
|
90
|
+
// Instead of MatSnackBar, we just use a generic console/alert fallback for now.
|
|
91
|
+
openSnackBar(message, className) {
|
|
92
|
+
}
|
|
93
|
+
clearOptions() {
|
|
94
|
+
this.pickupOptions = [];
|
|
95
|
+
this.destOptions = [];
|
|
96
|
+
this.ownerOptions = [];
|
|
97
|
+
this.customerOptions = [];
|
|
98
|
+
this.unitOptions = [];
|
|
99
|
+
this.materialOptions = [];
|
|
100
|
+
this.jcodeOptions = [];
|
|
101
|
+
this.routeNameOptions = [];
|
|
102
|
+
}
|
|
103
|
+
makeOptions(item) {
|
|
104
|
+
// Porting V1 makeOptions safely
|
|
105
|
+
if (item.order_number) {
|
|
106
|
+
if (this.jcodeOptions.findIndex((elem) => elem.job_id === item.job_id) === -1) {
|
|
107
|
+
this.jcodeOptions.push({ job_code: item.order_number, job_id: item.job_id });
|
|
108
|
+
}
|
|
109
|
+
// Assuming tasks structure if Mapbox Route logic was merged, else driver_list from V1
|
|
110
|
+
const driver_list = item.driver_list || [];
|
|
111
|
+
if (driver_list.length > 0) {
|
|
112
|
+
driver_list.forEach((driver) => {
|
|
113
|
+
if (this.driverOption.findIndex((elem) => elem === driver.driver_name) === -1) {
|
|
114
|
+
this.driverOption.push(driver.driver_name);
|
|
115
|
+
}
|
|
116
|
+
if (this.truckingCompanayOption.findIndex((elem) => elem === driver.trucking_company) === -1) {
|
|
117
|
+
this.truckingCompanayOption.push(driver.trucking_company);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (item.pickup_location && this.pickupOptions.findIndex((elem) => elem === item.pickup_location) === -1)
|
|
123
|
+
this.pickupOptions.push(item.pickup_location);
|
|
124
|
+
if (item.delivery_location && this.destOptions.findIndex((elem) => elem === item.delivery_location) === -1)
|
|
125
|
+
this.destOptions.push(item.delivery_location);
|
|
126
|
+
if (item.customer_name && this.customerOptions.findIndex((customer) => customer === item.customer_name) === -1)
|
|
127
|
+
this.customerOptions.push(item.customer_name);
|
|
128
|
+
if (item.unit && this.unitOptions.findIndex((elem) => elem === item.unit) === -1)
|
|
129
|
+
this.unitOptions.push(item.unit);
|
|
130
|
+
if (item.material && this.materialOptions.findIndex((elem) => elem === item.material) === -1)
|
|
131
|
+
this.materialOptions.push(item.material);
|
|
132
|
+
if (item.route_name && this.routeNameOptions.findIndex((elem) => elem === item.route_name) === -1)
|
|
133
|
+
this.routeNameOptions.push(item.route_name);
|
|
134
|
+
}
|
|
135
|
+
setdictValue(key, value) {
|
|
136
|
+
this.dict.set(key, value);
|
|
137
|
+
}
|
|
138
|
+
getdictValue(key) {
|
|
139
|
+
const val = this.dict.get(key);
|
|
140
|
+
return val ? JSON.parse(val) : null;
|
|
141
|
+
}
|
|
142
|
+
removedictValue(key) {
|
|
143
|
+
this.dict.delete(key);
|
|
144
|
+
}
|
|
145
|
+
filter(value, filters) {
|
|
146
|
+
if (typeof value !== 'string') {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const filterValue = value.toLowerCase();
|
|
150
|
+
if (filterValue === '') {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const searchResults = [];
|
|
154
|
+
this.unitOptions.forEach((unit) => {
|
|
155
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
156
|
+
searchResults.push({ type: 'unit', label: unit, value: unit });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.customerOptions.forEach((unit) => {
|
|
160
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
161
|
+
searchResults.push({ type: 'customer', label: unit, value: unit });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
if (this.materialOptions && this.materialOptions.length) {
|
|
165
|
+
this.materialOptions.forEach((unit) => {
|
|
166
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
167
|
+
searchResults.push({ type: 'material', label: unit, value: unit });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
this.pickupOptions.forEach((unit) => {
|
|
172
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
173
|
+
searchResults.push({
|
|
174
|
+
type: 'pickup location',
|
|
175
|
+
label: unit,
|
|
176
|
+
value: unit,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
this.destOptions.forEach((unit) => {
|
|
181
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
182
|
+
searchResults.push({
|
|
183
|
+
type: 'destination location',
|
|
184
|
+
label: unit,
|
|
185
|
+
value: unit,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
this.jcodeOptions.forEach((unit) => {
|
|
190
|
+
if (unit.job_code.toLowerCase().includes(filterValue)) {
|
|
191
|
+
searchResults.push({
|
|
192
|
+
type: 'job',
|
|
193
|
+
label: unit['job_code'],
|
|
194
|
+
value: unit,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
this.driverOption.forEach((unit) => {
|
|
199
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
200
|
+
searchResults.push({ type: 'Driver', label: unit, value: unit });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
this.truckingCompanayOption.forEach((unit) => {
|
|
204
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
205
|
+
searchResults.push({
|
|
206
|
+
type: 'Trucking Company',
|
|
207
|
+
label: unit,
|
|
208
|
+
value: unit,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
this.routeNameOptions.forEach((unit) => {
|
|
213
|
+
if (unit.toLowerCase().includes(filterValue)) {
|
|
214
|
+
searchResults.push({ type: 'Route name', label: unit, value: unit });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// Filter out options that are already selected in the filters array
|
|
218
|
+
const searchDict = {};
|
|
219
|
+
filters.forEach((filter) => {
|
|
220
|
+
searchDict[filter['name'] + filter['type']] = filter;
|
|
221
|
+
});
|
|
222
|
+
const furtherFilter = [];
|
|
223
|
+
searchResults.forEach((search) => {
|
|
224
|
+
if (!(search['label'] + search['type'] in searchDict)) {
|
|
225
|
+
furtherFilter.push(search);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
return furtherFilter;
|
|
229
|
+
}
|
|
230
|
+
getSearchResults(list, filterval) {
|
|
231
|
+
return list.filter((element) => {
|
|
232
|
+
const result_list_boolean = [];
|
|
233
|
+
if (filterval.length > 0) {
|
|
234
|
+
if (filterval[0]['type'] === 'unit') {
|
|
235
|
+
result_list_boolean.push(filterval[0]['name'] === element['unit']);
|
|
236
|
+
}
|
|
237
|
+
if (filterval[0]['type'] === 'customer') {
|
|
238
|
+
result_list_boolean.push(filterval[0]['name'] === element['customer_name']);
|
|
239
|
+
}
|
|
240
|
+
if (filterval[0]['type'] === 'material') {
|
|
241
|
+
result_list_boolean.push(filterval[0]['name'] === element['material']);
|
|
242
|
+
}
|
|
243
|
+
if (filterval[0]['type'] === 'pickup location') {
|
|
244
|
+
result_list_boolean.push(filterval[0]['name'] === element['pickup_location']);
|
|
245
|
+
}
|
|
246
|
+
if (filterval[0]['type'] === 'destination location') {
|
|
247
|
+
result_list_boolean.push(filterval[0]['name'] === element['delivery_location']);
|
|
248
|
+
}
|
|
249
|
+
if (filterval[0]['type'] === 'job') {
|
|
250
|
+
result_list_boolean.push(filterval[0]['name'] === element['order_number']);
|
|
251
|
+
}
|
|
252
|
+
if (filterval[0]['type'] === 'Route name') {
|
|
253
|
+
result_list_boolean.push(filterval[0]['name'] === element['route_name']);
|
|
254
|
+
}
|
|
255
|
+
if (filterval[0]['type'] === 'Driver') {
|
|
256
|
+
const driverList = element.driver_list || [];
|
|
257
|
+
const index = driverList.findIndex((ele) => {
|
|
258
|
+
return filterval[0]['name'] === ele['driver_name'];
|
|
259
|
+
});
|
|
260
|
+
if (index !== -1)
|
|
261
|
+
result_list_boolean.push(true);
|
|
262
|
+
}
|
|
263
|
+
if (filterval[0]['type'] === 'Trucking Company') {
|
|
264
|
+
const driverList = element.driver_list || [];
|
|
265
|
+
const index = driverList.findIndex((ele) => {
|
|
266
|
+
return filterval[0]['name'] === ele['trucking_company'];
|
|
267
|
+
});
|
|
268
|
+
if (index !== -1)
|
|
269
|
+
result_list_boolean.push(true);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (result_list_boolean.length > 0) {
|
|
273
|
+
return result_list_boolean.reduce((prev, curr) => prev && curr);
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
fetchUnitsList() {
|
|
279
|
+
return new Promise((resolve) => {
|
|
280
|
+
if (!this.unitsList) {
|
|
281
|
+
this.getData('unit/list/view').subscribe((res) => {
|
|
282
|
+
if (res) {
|
|
283
|
+
this.unitsList = res.data;
|
|
284
|
+
resolve(this.unitsList);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
resolve(this.unitsList);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
fetchMaterialsListForCustomer() {
|
|
294
|
+
return new Promise((resolve) => {
|
|
295
|
+
if (!this.materialsListForCustomer) {
|
|
296
|
+
this.getData('material/list/view').subscribe((res) => {
|
|
297
|
+
this.materialsListForCustomer = this.filtermaterialList(res.data);
|
|
298
|
+
resolve(this.materialsListForCustomer);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
resolve(this.materialsListForCustomer);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
filtermaterialList(list) {
|
|
307
|
+
let meterialList = [];
|
|
308
|
+
list.forEach((item) => {
|
|
309
|
+
if (item.sub && item.sub.length > 0) {
|
|
310
|
+
item.sub.forEach((subItem) => {
|
|
311
|
+
if (subItem.add_to_marketplace) {
|
|
312
|
+
subItem.material = item.material;
|
|
313
|
+
subItem.material_id = item.material_id;
|
|
314
|
+
subItem.label = item.material + ' | ' + subItem.sub_material;
|
|
315
|
+
meterialList.push(subItem);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
meterialList.sort((a, b) => a.label.localeCompare(b.label));
|
|
321
|
+
return meterialList;
|
|
322
|
+
}
|
|
323
|
+
fetchCustomersList() {
|
|
324
|
+
return new Promise((resolve) => {
|
|
325
|
+
if (!this.customersList) {
|
|
326
|
+
this.getData('company/list/view').subscribe((res) => {
|
|
327
|
+
if (res) {
|
|
328
|
+
this.customersList = res.data;
|
|
329
|
+
resolve(this.customersList);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
resolve(this.customersList);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
fetchLocationlist() {
|
|
339
|
+
return new Promise((resolve, reject) => {
|
|
340
|
+
this.getRestData('locations/all').subscribe({
|
|
341
|
+
next: (res) => {
|
|
342
|
+
if (res) {
|
|
343
|
+
this.locationList = res.data.map((object) => {
|
|
344
|
+
object.formatted_address = `${object.name} | ${object.street} ${object.city}, ${object.state} ${object.zip}`;
|
|
345
|
+
return {
|
|
346
|
+
city: object.city,
|
|
347
|
+
lng: object.longitude,
|
|
348
|
+
location_id: object.location_id,
|
|
349
|
+
lat: object.latitude,
|
|
350
|
+
name: object.name,
|
|
351
|
+
state: object.state,
|
|
352
|
+
street: object.street,
|
|
353
|
+
zip: object.zip,
|
|
354
|
+
formatted_address: object.formatted_address
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
resolve(this.locationList);
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
error: (err) => {
|
|
361
|
+
console.error(err);
|
|
362
|
+
reject(err);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
368
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UtilsService, providedIn: 'root' });
|
|
369
|
+
}
|
|
370
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UtilsService, decorators: [{
|
|
371
|
+
type: Injectable,
|
|
372
|
+
args: [{ providedIn: 'root' }]
|
|
373
|
+
}] });
|
|
374
|
+
|
|
375
|
+
class MapboxService {
|
|
376
|
+
map;
|
|
377
|
+
mapContainer = null;
|
|
378
|
+
markerOriginList = new Map();
|
|
379
|
+
markerDestinationList = new Map();
|
|
380
|
+
activeRoutesRegistry = new Map();
|
|
381
|
+
pathCache = new Map();
|
|
382
|
+
// Native 3D Simulation Engine
|
|
383
|
+
svgOverlay = null;
|
|
384
|
+
activeArcs = {};
|
|
385
|
+
initiatecoolmap = new BehaviorSubject(true);
|
|
386
|
+
reintiatecoolmap = this.initiatecoolmap.asObservable();
|
|
387
|
+
isMapReady = signal(false, ...(ngDevMode ? [{ debugName: "isMapReady" }] : []));
|
|
388
|
+
bounds = new mapboxgl.LngLatBounds();
|
|
389
|
+
originDestinationCordinates = [];
|
|
390
|
+
padding;
|
|
391
|
+
windowActualHeightWidth = { availHeight: 0 };
|
|
392
|
+
popup;
|
|
393
|
+
customTopForCustomer;
|
|
394
|
+
utils = inject(UtilsService);
|
|
395
|
+
config = inject(COOLMAP_CONFIG);
|
|
396
|
+
currentStyleIsDark = false;
|
|
397
|
+
constructor() {
|
|
398
|
+
this.customTopForCustomer = this.config.repository === 'customer' ? 65 : 0;
|
|
399
|
+
if (typeof window !== 'undefined') {
|
|
400
|
+
this.windowActualHeightWidth.availHeight = window.innerHeight > window.screen.availHeight ? window.innerHeight : window.screen.availHeight;
|
|
401
|
+
window.addEventListener('resize', this.onResize.bind(this));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
updateTheme(isDark) {
|
|
405
|
+
if (!this.map) {
|
|
406
|
+
this.currentStyleIsDark = isDark;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (this.currentStyleIsDark === isDark)
|
|
410
|
+
return;
|
|
411
|
+
this.currentStyleIsDark = isDark;
|
|
412
|
+
const style = isDark
|
|
413
|
+
? 'mapbox://styles/mapbox/dark-v11'
|
|
414
|
+
: 'mapbox://styles/mapbox/light-v11';
|
|
415
|
+
this.map.setStyle(style);
|
|
416
|
+
this.map.once('style.load', () => {
|
|
417
|
+
this.reAddLayers();
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
getRegistryKeys() {
|
|
421
|
+
return Array.from(this.activeRoutesRegistry.keys());
|
|
422
|
+
}
|
|
423
|
+
// Renamed to initializeMap to preserve the existing bindings in coolmap.component.ts
|
|
424
|
+
initializeMap(el, isDark = false) {
|
|
425
|
+
this.currentStyleIsDark = isDark;
|
|
426
|
+
return new Promise((resolve, reject) => {
|
|
427
|
+
this.mapContainer = el;
|
|
428
|
+
if (this.map)
|
|
429
|
+
this.map.remove();
|
|
430
|
+
const token = typeof window !== 'undefined' ? window.__env?.mapboxAccessToken || '' : '';
|
|
431
|
+
mapboxgl.accessToken = token;
|
|
432
|
+
this.map = new mapboxgl.Map({
|
|
433
|
+
container: el,
|
|
434
|
+
style: isDark ? 'mapbox://styles/mapbox/dark-v11' : 'mapbox://styles/mapbox/light-v11',
|
|
435
|
+
center: [-77.036873, 38.907192],
|
|
436
|
+
zoom: 10,
|
|
437
|
+
bearing: 0,
|
|
438
|
+
pitch: 65,
|
|
439
|
+
interactive: true,
|
|
440
|
+
attributionControl: false
|
|
441
|
+
});
|
|
442
|
+
this.map.addControl(new mapboxgl.AttributionControl({ compact: true }), 'bottom-right');
|
|
443
|
+
this.map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-right');
|
|
444
|
+
this.map.once('load', (res) => {
|
|
445
|
+
this.isMapReady.set(true);
|
|
446
|
+
resolve(res);
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
// Animated gradient path
|
|
451
|
+
loadMapProperty(pinRouteGeojson, index, unit, routeProps, bottom) {
|
|
452
|
+
return new Promise((resolve) => {
|
|
453
|
+
if (!this.map || !this.isMapReady())
|
|
454
|
+
return resolve(false);
|
|
455
|
+
const origin = pinRouteGeojson.features[0].geometry.coordinates[0];
|
|
456
|
+
const linecolor = unit === 'Ton' ? '#ff7272' : unit === 'Load' ? '#a3c52e' : '#ae23d1';
|
|
457
|
+
const destination = pinRouteGeojson.features[0].geometry.coordinates[pinRouteGeojson.features[0].geometry.coordinates.length - 1];
|
|
458
|
+
this.extendBound(pinRouteGeojson.features[0].geometry.coordinates, true).then(() => {
|
|
459
|
+
const point = {
|
|
460
|
+
type: 'FeatureCollection',
|
|
461
|
+
features: [
|
|
462
|
+
{ type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: origin } },
|
|
463
|
+
],
|
|
464
|
+
};
|
|
465
|
+
const lineDistance = turf.length(pinRouteGeojson.features[0]);
|
|
466
|
+
const arc = [];
|
|
467
|
+
const steps = 10 * pinRouteGeojson.features[0].geometry.coordinates.length;
|
|
468
|
+
for (let i = 0; i < lineDistance; i += lineDistance / steps) {
|
|
469
|
+
const segment = turf.along(pinRouteGeojson.features[0], i);
|
|
470
|
+
arc.push(segment.geometry.coordinates);
|
|
471
|
+
}
|
|
472
|
+
pinRouteGeojson.features[0].geometry.coordinates = arc;
|
|
473
|
+
const pinRoute = pinRouteGeojson.features[0].geometry.coordinates;
|
|
474
|
+
if (pinRoute && pinRoute.length) {
|
|
475
|
+
const marker = new mapboxgl.Marker(document.createElement('div'))
|
|
476
|
+
.setLngLat(pinRoute[0])
|
|
477
|
+
.addTo(this.map)
|
|
478
|
+
.togglePopup();
|
|
479
|
+
if (this.map.getSource(`line${index}`)) {
|
|
480
|
+
this.removeRouteAndMarker(index).then(() => { });
|
|
481
|
+
}
|
|
482
|
+
this.map.addSource(`line${index}`, {
|
|
483
|
+
type: 'geojson',
|
|
484
|
+
lineMetrics: true,
|
|
485
|
+
data: pinRouteGeojson,
|
|
486
|
+
});
|
|
487
|
+
this.map.addLayer({
|
|
488
|
+
type: 'line',
|
|
489
|
+
source: `line${index}`,
|
|
490
|
+
id: `line${index}`,
|
|
491
|
+
paint: {
|
|
492
|
+
'line-width': 2,
|
|
493
|
+
'line-gradient': [
|
|
494
|
+
'interpolate', ['linear'], ['line-progress'],
|
|
495
|
+
0, unit === 'Ton' ? '#d7f7e4' : unit === 'Load' ? '#c9d8f5' : '#f5dcc1',
|
|
496
|
+
1, unit === 'Ton' ? '#ff7272' : unit === 'Load' ? '#a3c52e' : '#ae23d1',
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
500
|
+
});
|
|
501
|
+
const dataSetForMap = {
|
|
502
|
+
counter: 0,
|
|
503
|
+
pinRouteGeojson,
|
|
504
|
+
steps,
|
|
505
|
+
point,
|
|
506
|
+
pointId: `point${index}`,
|
|
507
|
+
marker,
|
|
508
|
+
pinRoute,
|
|
509
|
+
lineId: `line${index}`,
|
|
510
|
+
index,
|
|
511
|
+
origin,
|
|
512
|
+
destination,
|
|
513
|
+
lineDistance,
|
|
514
|
+
linecolor,
|
|
515
|
+
route: routeProps,
|
|
516
|
+
routeType: 'addroute',
|
|
517
|
+
isViewRoute: true,
|
|
518
|
+
};
|
|
519
|
+
this.createMarker(dataSetForMap);
|
|
520
|
+
this.map.on('mouseenter', `line${index}`, (e) => {
|
|
521
|
+
this.map.setPaintProperty(`line${index}`, 'line-width', 5);
|
|
522
|
+
this.map.setPaintProperty(`line${index}`, 'line-opacity', 1);
|
|
523
|
+
const datasetForPopup = {
|
|
524
|
+
coordinate: [e.lngLat.lng, e.lngLat.lat],
|
|
525
|
+
pickup: routeProps?.pickup_location || '',
|
|
526
|
+
drop: routeProps?.delivery_location || '',
|
|
527
|
+
routeType: routeProps?.project ? 'Project' : 'Route',
|
|
528
|
+
title: routeProps?.project ? routeProps.project : routeProps?.route_name || '',
|
|
529
|
+
material: routeProps?.material || '',
|
|
530
|
+
type: routeProps?.unit || '',
|
|
531
|
+
};
|
|
532
|
+
if (this.popup)
|
|
533
|
+
this.popup.remove();
|
|
534
|
+
this.createPopup(datasetForPopup);
|
|
535
|
+
});
|
|
536
|
+
this.map.on('mouseleave', `line${index}`, () => {
|
|
537
|
+
this.map.setPaintProperty(`line${index}`, 'line-width', 2);
|
|
538
|
+
if (this.popup)
|
|
539
|
+
this.popup.remove();
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
this.map.once('idle', () => {
|
|
544
|
+
resolve(true);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
createMarker(routeDetails) {
|
|
549
|
+
if (!this.map || !routeDetails.origin[0] || !routeDetails.origin[1] || !routeDetails.destination[0] || !routeDetails.destination[1])
|
|
550
|
+
return;
|
|
551
|
+
const popup = new mapboxgl.Popup({ closeButton: false }).setHTML('<b>Pickup: </b>' + (routeDetails.route?.pickup_location || ''));
|
|
552
|
+
const popupForDestination = new mapboxgl.Popup({ closeButton: false }).setHTML('<b>Delivery: </b>' + (routeDetails.route?.delivery_location || ''));
|
|
553
|
+
// Origin Marker (Pickup)
|
|
554
|
+
const el = document.createElement('div');
|
|
555
|
+
el.className = 'cursor-pointer active:scale-95 transition-transform duration-200 drop-shadow-lg';
|
|
556
|
+
const pickupLabel = 'P';
|
|
557
|
+
el.innerHTML = `
|
|
558
|
+
<svg width="30" height="38" viewBox="0 0 30 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
559
|
+
<path d="M15 0C6.71573 0 0 6.71573 0 15C0 26.25 15 38 15 38C15 38 30 26.25 30 15C30 6.71573 23.2843 0 15 0Z" fill="${routeDetails.linecolor}"/>
|
|
560
|
+
<circle cx="15" cy="15" r="9" fill="white"/>
|
|
561
|
+
<text x="15" y="19" text-anchor="middle" fill="black" style="font-family: Inter, sans-serif; font-size: 13px; font-weight: 800;">${pickupLabel}</text>
|
|
562
|
+
</svg>
|
|
563
|
+
`;
|
|
564
|
+
const originMarker = new mapboxgl.Marker({ element: el, anchor: 'bottom' })
|
|
565
|
+
.setPopup(popup)
|
|
566
|
+
.setLngLat(routeDetails.origin)
|
|
567
|
+
.addTo(this.map);
|
|
568
|
+
originMarker.getElement().addEventListener('mouseenter', () => originMarker.togglePopup());
|
|
569
|
+
originMarker.getElement().addEventListener('mouseleave', () => originMarker.togglePopup());
|
|
570
|
+
// Destination Marker (Delivery)
|
|
571
|
+
const elementForDestination = document.createElement('div');
|
|
572
|
+
elementForDestination.className = 'cursor-pointer active:scale-95 transition-transform duration-200 drop-shadow-lg';
|
|
573
|
+
const deliveryLabel = 'D';
|
|
574
|
+
elementForDestination.innerHTML = `
|
|
575
|
+
<svg width="30" height="38" viewBox="0 0 30 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
576
|
+
<path d="M15 0C6.71573 0 0 6.71573 0 15C0 26.25 15 38 15 38C15 38 30 26.25 30 15C30 6.71573 23.2843 0 15 0Z" fill="${routeDetails.linecolor}"/>
|
|
577
|
+
<circle cx="15" cy="15" r="9" fill="white"/>
|
|
578
|
+
<text x="15" y="19" text-anchor="middle" fill="black" style="font-family: Inter, sans-serif; font-size: 13px; font-weight: 800;">${deliveryLabel}</text>
|
|
579
|
+
</svg>
|
|
580
|
+
`;
|
|
581
|
+
const destinationMarker = new mapboxgl.Marker({ element: elementForDestination, anchor: 'bottom' })
|
|
582
|
+
.setPopup(popupForDestination)
|
|
583
|
+
.setLngLat(routeDetails.destination)
|
|
584
|
+
.addTo(this.map);
|
|
585
|
+
destinationMarker.getElement().addEventListener('mouseenter', () => destinationMarker.togglePopup());
|
|
586
|
+
destinationMarker.getElement().addEventListener('mouseleave', () => destinationMarker.togglePopup());
|
|
587
|
+
const id = `${routeDetails.routeType}-${routeDetails.index}`;
|
|
588
|
+
this.markerOriginList.set(id, originMarker);
|
|
589
|
+
this.markerDestinationList.set(id, destinationMarker);
|
|
590
|
+
// True 3D Elevated Arc Simulation Engine using Native SVG Overlays
|
|
591
|
+
try {
|
|
592
|
+
this.initSvgOverlay();
|
|
593
|
+
const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
594
|
+
pathEl.setAttribute('fill', 'none');
|
|
595
|
+
pathEl.setAttribute('stroke-width', '3');
|
|
596
|
+
pathEl.style.cursor = 'pointer';
|
|
597
|
+
pathEl.style.pointerEvents = 'stroke'; // Allow interactivity!
|
|
598
|
+
const gradId = `arc-grad-${id}`;
|
|
599
|
+
const defs = this.svgOverlay.querySelector('defs');
|
|
600
|
+
let grad = defs.querySelector(`#${gradId}`);
|
|
601
|
+
if (!grad) {
|
|
602
|
+
grad = document.createElementNS('http://www.w3.org/2000/svg', 'linearGradient');
|
|
603
|
+
grad.setAttribute('id', gradId);
|
|
604
|
+
defs.appendChild(grad);
|
|
605
|
+
}
|
|
606
|
+
grad.innerHTML = `
|
|
607
|
+
<stop offset="0%" stop-color="${routeDetails.linecolor}" stop-opacity="1"/>
|
|
608
|
+
<stop offset="100%" stop-color="#ffffff" stop-opacity="0.8"/>
|
|
609
|
+
`;
|
|
610
|
+
pathEl.setAttribute('stroke', `url(#${gradId})`);
|
|
611
|
+
this.svgOverlay.appendChild(pathEl);
|
|
612
|
+
this.activeArcs[id] = {
|
|
613
|
+
origin: routeDetails.origin,
|
|
614
|
+
destination: routeDetails.destination,
|
|
615
|
+
color: routeDetails.linecolor,
|
|
616
|
+
pathEl
|
|
617
|
+
};
|
|
618
|
+
// Native tooltips hovering strictly matching V1 Deck.GL behaviors
|
|
619
|
+
pathEl.addEventListener('mouseenter', (e) => {
|
|
620
|
+
pathEl.setAttribute('stroke-width', '6');
|
|
621
|
+
// Find geo coordinate exactly under cursor natively
|
|
622
|
+
const rect = this.svgOverlay.getBoundingClientRect();
|
|
623
|
+
const lngLat = this.map.unproject([e.clientX - rect.left, e.clientY - rect.top]);
|
|
624
|
+
const payload = {
|
|
625
|
+
layer: { props: { data: { route: routeDetails.route, index: routeDetails.index, routeType: routeDetails.routeType } } },
|
|
626
|
+
coordinate: [lngLat.lng, lngLat.lat],
|
|
627
|
+
color: routeDetails.linecolor
|
|
628
|
+
};
|
|
629
|
+
this.showRoutePopup(payload, e, routeDetails.isViewRoute);
|
|
630
|
+
});
|
|
631
|
+
pathEl.addEventListener('mouseleave', () => {
|
|
632
|
+
pathEl.setAttribute('stroke-width', '3');
|
|
633
|
+
const payload = { color: null, layer: { props: { data: { index: routeDetails.index, routeType: routeDetails.routeType } } } };
|
|
634
|
+
this.showRoutePopup(payload, null, routeDetails.isViewRoute);
|
|
635
|
+
});
|
|
636
|
+
this.updateSvgPaths(); // Force plot
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
console.warn("Native overarching arc generation skipped:", e);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
showRoutePopup(arcDetails, event, isViewRoute) {
|
|
643
|
+
const data = arcDetails.layer.props.data;
|
|
644
|
+
const layerId = isViewRoute
|
|
645
|
+
? `line${data.index}`
|
|
646
|
+
: `route-layer-${data.routeType || 'jobcode'}-${data.index}`;
|
|
647
|
+
if (this.popup) {
|
|
648
|
+
this.popup.remove();
|
|
649
|
+
if (this.map.getLayer(layerId)) {
|
|
650
|
+
this.map.setPaintProperty(layerId, 'line-width', 2);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (arcDetails.color && this.map.getLayer(layerId) && this.map.getLayoutProperty(layerId, 'visibility') !== 'none') {
|
|
654
|
+
this.map.setPaintProperty(layerId, 'line-width', 5);
|
|
655
|
+
// Removed line-opacity overwrite here because bezier lines inherit opacity and break if set raw on a gradient.
|
|
656
|
+
const datasetForPopup = {
|
|
657
|
+
coordinate: arcDetails.coordinate,
|
|
658
|
+
pickup: arcDetails.layer.props.data.route.pickup_location || '',
|
|
659
|
+
drop: arcDetails.layer.props.data.route.delivery_location || '',
|
|
660
|
+
jobCode: arcDetails.layer.props.data.route.project ? arcDetails.layer.props.data.route.order_number : '',
|
|
661
|
+
customer: arcDetails.layer.props.data.route.project ? arcDetails.layer.props.data.route.customer_name : '',
|
|
662
|
+
routeType: arcDetails.layer.props.data.route.project ? 'Project' : 'Route',
|
|
663
|
+
title: arcDetails.layer.props.data.route.project ? arcDetails.layer.props.data.route.project : arcDetails.layer.props.data.route.route_name || '',
|
|
664
|
+
material: arcDetails.layer.props.data.route.material || '',
|
|
665
|
+
type: arcDetails.layer.props.data.route.unit || '',
|
|
666
|
+
};
|
|
667
|
+
this.createPopup(datasetForPopup);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
createPopup(datasetForPopup) {
|
|
671
|
+
if (!this.map)
|
|
672
|
+
return;
|
|
673
|
+
this.popup = new mapboxgl.Popup({
|
|
674
|
+
closeButton: false,
|
|
675
|
+
closeOnClick: false,
|
|
676
|
+
closeOnMove: true,
|
|
677
|
+
anchor: 'bottom-left',
|
|
678
|
+
});
|
|
679
|
+
// Extract location short names
|
|
680
|
+
const pickupDisplay = datasetForPopup.pickup?.split('|')[1] || datasetForPopup.pickup || 'Unknown Pickup';
|
|
681
|
+
const dropDisplay = datasetForPopup.drop?.split('|')[1] || datasetForPopup.drop || 'Unknown Delivery';
|
|
682
|
+
this.popup
|
|
683
|
+
.setLngLat(datasetForPopup.coordinate)
|
|
684
|
+
.setHTML(`<div class="destination p-3 bg-white dark:bg-slate-800 rounded shadow-lg text-sm text-gray-800 dark:text-gray-200">
|
|
685
|
+
<div class="duration space-y-1 mb-2">
|
|
686
|
+
<p class="pickprt"><b class="text-gray-900 dark:text-white">Pickup Location:</b> ${pickupDisplay}</p>
|
|
687
|
+
<p class="dropprt"><b class="text-gray-900 dark:text-white">Drop Location:</b> ${dropDisplay}</p>
|
|
688
|
+
</div>
|
|
689
|
+
${datasetForPopup.jobCode ? `<span class="block"><b>Job Code:</b> ${datasetForPopup.jobCode}</span>` : ''}
|
|
690
|
+
${datasetForPopup.customer ? `<span class="block"><b>Customer:</b> ${datasetForPopup.customer}</span>` : ''}
|
|
691
|
+
<span class="block"><b>${datasetForPopup.routeType} Name:</b> ${datasetForPopup.title}</span>
|
|
692
|
+
${datasetForPopup.material ? `<span class="block"><b>Material:</b> ${datasetForPopup.material}</span>` : ''}
|
|
693
|
+
<span class="block"><b>Type:</b> ${datasetForPopup.type}</span>
|
|
694
|
+
</div>`)
|
|
695
|
+
.addTo(this.map);
|
|
696
|
+
}
|
|
697
|
+
// Draw Line
|
|
698
|
+
drawLine(cordinates, index, route, enablefitbound, routeType, isStyleRefresh) {
|
|
699
|
+
return new Promise((resolve) => {
|
|
700
|
+
if (!this.map || !this.isMapReady()) {
|
|
701
|
+
resolve();
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
// Calculate bounds for this specific route
|
|
705
|
+
const routeBounds = new mapboxgl.LngLatBounds();
|
|
706
|
+
if (cordinates && cordinates.length > 0) {
|
|
707
|
+
cordinates.forEach((coord) => routeBounds.extend(coord));
|
|
708
|
+
}
|
|
709
|
+
const registryId = `${routeType}-${index}`;
|
|
710
|
+
// Register the route with its coordinates and pre-calculated bounds
|
|
711
|
+
this.activeRoutesRegistry.set(registryId, {
|
|
712
|
+
cordinates,
|
|
713
|
+
route,
|
|
714
|
+
routeType,
|
|
715
|
+
bounds: routeBounds,
|
|
716
|
+
index
|
|
717
|
+
});
|
|
718
|
+
let origin = cordinates[0];
|
|
719
|
+
let destination = cordinates[cordinates.length - 1];
|
|
720
|
+
if (origin[0] && origin[1] && destination && destination[0] && destination[1]) {
|
|
721
|
+
// Execute plotting logic IMMEDIATELY to trigger the map's render cycle
|
|
722
|
+
const linecolor = this.provideLineColor(route['unit'], routeType);
|
|
723
|
+
if (enablefitbound) {
|
|
724
|
+
const padTop = Math.max(0, (this.padding?.top || 0) + this.customTopForCustomer + 50);
|
|
725
|
+
const padBot = Math.max(0, (this.padding?.bottom || 0) + (this.windowActualHeightWidth.availHeight - (window.innerHeight - 65)) + 50);
|
|
726
|
+
const padLeft = Math.max(0, (this.padding?.left || 0) + 350);
|
|
727
|
+
const padRight = Math.max(0, (this.padding?.right || 0) + 50);
|
|
728
|
+
const customPadding = { top: padTop, bottom: padBot, left: padLeft, right: padRight };
|
|
729
|
+
this.map.fitBounds([origin, destination], { padding: customPadding, pitch: 65 }, { fitboundCompleteJob: true });
|
|
730
|
+
}
|
|
731
|
+
const sourceId = `route-source-${routeType}-${index}`;
|
|
732
|
+
const layerId = `route-layer-${routeType}-${index}`;
|
|
733
|
+
if (this.map.getSource(sourceId)) {
|
|
734
|
+
this.removeRouteAndMarker(index, routeType).then(() => { });
|
|
735
|
+
}
|
|
736
|
+
this.map.addSource(sourceId, {
|
|
737
|
+
type: 'geojson',
|
|
738
|
+
data: {
|
|
739
|
+
type: 'Feature',
|
|
740
|
+
properties: {},
|
|
741
|
+
geometry: { type: 'LineString', coordinates: cordinates },
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
this.map.addLayer({
|
|
745
|
+
id: layerId,
|
|
746
|
+
type: 'line',
|
|
747
|
+
source: sourceId,
|
|
748
|
+
paint: { 'line-color': linecolor, 'line-width': 2 },
|
|
749
|
+
layout: { 'line-cap': 'round', 'line-join': 'round' },
|
|
750
|
+
});
|
|
751
|
+
if (!isStyleRefresh) {
|
|
752
|
+
const dataSetForMap = { origin, destination, index, linecolor, route, routeType };
|
|
753
|
+
this.createMarker(dataSetForMap);
|
|
754
|
+
}
|
|
755
|
+
this.map.on('mouseenter', layerId, (e) => {
|
|
756
|
+
if (this.popup)
|
|
757
|
+
this.popup.remove();
|
|
758
|
+
this.map.setPaintProperty(layerId, 'line-width', 5);
|
|
759
|
+
this.map.setPaintProperty(layerId, 'line-opacity', 1);
|
|
760
|
+
const datasetForPopup = {
|
|
761
|
+
coordinate: [e.lngLat.lng, e.lngLat.lat],
|
|
762
|
+
pickup: route.pickup_location || '',
|
|
763
|
+
drop: route.delivery_location || '',
|
|
764
|
+
jobCode: route.project ? route.order_number : null,
|
|
765
|
+
customer: route.project ? route.customer_name : null,
|
|
766
|
+
routeType: route.project ? 'Project' : 'Route',
|
|
767
|
+
title: route.project ? route.project : route.route_name || '',
|
|
768
|
+
material: route.material || '',
|
|
769
|
+
type: route.unit || '',
|
|
770
|
+
};
|
|
771
|
+
this.createPopup(datasetForPopup);
|
|
772
|
+
});
|
|
773
|
+
this.map.on('mouseleave', layerId, () => {
|
|
774
|
+
this.map.setPaintProperty(layerId, 'line-width', 2);
|
|
775
|
+
if (this.popup)
|
|
776
|
+
this.popup.remove();
|
|
777
|
+
});
|
|
778
|
+
// SAFETY: Use a timeout to ensure resolve() is always called even if 'idle' event skips
|
|
779
|
+
let resolved = false;
|
|
780
|
+
const complete = () => {
|
|
781
|
+
if (!resolved) {
|
|
782
|
+
resolved = true;
|
|
783
|
+
resolve();
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
this.map.once('idle', complete);
|
|
787
|
+
setTimeout(complete, 500); // 500ms safety timeout
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
resolve();
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
provideLineColor(unitType, type) {
|
|
795
|
+
let checkType = type && !['jobrouteList', 'addroute'].includes(type) ? true : false;
|
|
796
|
+
let color;
|
|
797
|
+
switch (unitType) {
|
|
798
|
+
case 'Ton':
|
|
799
|
+
color = checkType ? '#39c471' : '#ff7272';
|
|
800
|
+
break;
|
|
801
|
+
case 'Load':
|
|
802
|
+
color = checkType ? '#326ad3' : '#a3c52e';
|
|
803
|
+
break;
|
|
804
|
+
case 'Hourly':
|
|
805
|
+
color = checkType ? '#ffad56' : '#ae23d1';
|
|
806
|
+
break;
|
|
807
|
+
default:
|
|
808
|
+
color = '#3b82f6';
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
return color;
|
|
812
|
+
}
|
|
813
|
+
hexToRGB(hex) {
|
|
814
|
+
return hex
|
|
815
|
+
.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b)
|
|
816
|
+
.substring(1)
|
|
817
|
+
.match(/.{2}/g)
|
|
818
|
+
.map((x) => parseInt(x, 16));
|
|
819
|
+
}
|
|
820
|
+
// --- SVG 3D Engine Methods ---
|
|
821
|
+
initSvgOverlay() {
|
|
822
|
+
if (this.svgOverlay || !this.mapContainer)
|
|
823
|
+
return;
|
|
824
|
+
this.svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
825
|
+
this.svgOverlay.style.position = 'absolute';
|
|
826
|
+
this.svgOverlay.style.top = '0';
|
|
827
|
+
this.svgOverlay.style.left = '0';
|
|
828
|
+
this.svgOverlay.style.width = '100%';
|
|
829
|
+
this.svgOverlay.style.height = '100%';
|
|
830
|
+
this.svgOverlay.style.pointerEvents = 'none';
|
|
831
|
+
this.svgOverlay.style.zIndex = '10';
|
|
832
|
+
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
|
833
|
+
this.svgOverlay.appendChild(defs);
|
|
834
|
+
this.mapContainer.appendChild(this.svgOverlay);
|
|
835
|
+
this.map.on('render', () => this.updateSvgPaths());
|
|
836
|
+
}
|
|
837
|
+
updateSvgPaths() {
|
|
838
|
+
if (!this.svgOverlay || !this.map)
|
|
839
|
+
return;
|
|
840
|
+
for (const key in this.activeArcs) {
|
|
841
|
+
const arc = this.activeArcs[key];
|
|
842
|
+
const p1 = this.map.project(arc.origin);
|
|
843
|
+
const p2 = this.map.project(arc.destination);
|
|
844
|
+
// Safety Guard: Check for Infinity and extreme numbers (Number.MAX_VALUE)
|
|
845
|
+
// which Mapbox can return during transitions.
|
|
846
|
+
// 1e15 is a safe threshold for JS math and SVG rendering engines.
|
|
847
|
+
const isUsable = (p) => Number.isFinite(p.x) && Math.abs(p.x) < 1e15 &&
|
|
848
|
+
Number.isFinite(p.y) && Math.abs(p.y) < 1e15;
|
|
849
|
+
if (!isUsable(p1) || !isUsable(p2))
|
|
850
|
+
continue;
|
|
851
|
+
const dx = p2.x - p1.x;
|
|
852
|
+
const dy = p2.y - p1.y;
|
|
853
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
854
|
+
// Secondary check for distance finiteness
|
|
855
|
+
if (!Number.isFinite(dist) || dist > 1e15)
|
|
856
|
+
continue;
|
|
857
|
+
const mx = (p1.x + p2.x) / 2;
|
|
858
|
+
let controlY = ((p1.y + p2.y) / 2) - (dist * 1.05);
|
|
859
|
+
// Final sanity check for calculated curve peaks
|
|
860
|
+
controlY = Math.max(100, controlY);
|
|
861
|
+
if (!Number.isFinite(controlY) || !Number.isFinite(mx))
|
|
862
|
+
continue;
|
|
863
|
+
// Update with rounded values to ensure clean and valid attribute data
|
|
864
|
+
arc.pathEl.setAttribute('d', `M ${p1.x.toFixed(2)} ${p1.y.toFixed(2)} Q ${mx.toFixed(2)} ${controlY.toFixed(2)} ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`);
|
|
865
|
+
// Sync the gradient positioning physically mapped to coordinate space
|
|
866
|
+
const grad = this.svgOverlay.querySelector(`#arc-grad-${key}`);
|
|
867
|
+
if (grad) {
|
|
868
|
+
const minX = Math.min(p1.x, p2.x);
|
|
869
|
+
const maxX = Math.max(p1.x, p2.x);
|
|
870
|
+
const minY = Math.min(p1.y, p2.y, controlY);
|
|
871
|
+
const maxY = Math.max(p1.y, p2.y, controlY);
|
|
872
|
+
const w = Math.max(1, maxX - minX);
|
|
873
|
+
const h = Math.max(1, maxY - minY);
|
|
874
|
+
grad.setAttribute('x1', `${((p1.x - minX) / w) * 100}%`);
|
|
875
|
+
grad.setAttribute('y1', `${((p1.y - minY) / h) * 100}%`);
|
|
876
|
+
grad.setAttribute('x2', `${((p2.x - minX) / w) * 100}%`);
|
|
877
|
+
grad.setAttribute('y2', `${((p2.y - minY) / h) * 100}%`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async removeRouteAndMarker(index, routeType = 'jobcode') {
|
|
882
|
+
const registryId = `${routeType}-${index}`;
|
|
883
|
+
// Clean up registry
|
|
884
|
+
if (this.activeRoutesRegistry.has(registryId)) {
|
|
885
|
+
this.activeRoutesRegistry.delete(registryId);
|
|
886
|
+
}
|
|
887
|
+
if (!this.map)
|
|
888
|
+
return true;
|
|
889
|
+
try {
|
|
890
|
+
if (this.activeArcs[registryId]) {
|
|
891
|
+
const arc = this.activeArcs[registryId];
|
|
892
|
+
if (arc.pathEl.parentNode)
|
|
893
|
+
arc.pathEl.parentNode.removeChild(arc.pathEl);
|
|
894
|
+
delete this.activeArcs[registryId];
|
|
895
|
+
}
|
|
896
|
+
if (this.svgOverlay) {
|
|
897
|
+
const grad = this.svgOverlay.querySelector(`defs #arc-grad-${registryId}`);
|
|
898
|
+
if (grad && grad.parentNode)
|
|
899
|
+
grad.parentNode.removeChild(grad);
|
|
900
|
+
}
|
|
901
|
+
const layerId = `route-layer-${routeType}-${index}`;
|
|
902
|
+
const sourceId = `route-source-${routeType}-${index}`;
|
|
903
|
+
this.map.getLayer(layerId) ? this.map.removeLayer(layerId) : null;
|
|
904
|
+
this.map.getLayer(`line${index}`) ? this.map.removeLayer(`line${index}`) : null;
|
|
905
|
+
this.map.getSource(sourceId) ? this.map.removeSource(sourceId) : null;
|
|
906
|
+
this.map.getSource(`line${index}`) ? this.map.removeSource(`line${index}`) : null;
|
|
907
|
+
this.markerOriginList.get(registryId)?.remove();
|
|
908
|
+
this.markerOriginList.delete(registryId);
|
|
909
|
+
this.markerDestinationList.get(registryId)?.remove();
|
|
910
|
+
this.markerDestinationList.delete(registryId);
|
|
911
|
+
this.markerOriginList.get(String(index))?.remove();
|
|
912
|
+
this.markerDestinationList.get(String(index))?.remove();
|
|
913
|
+
if (this.popup)
|
|
914
|
+
this.popup.remove();
|
|
915
|
+
return true;
|
|
916
|
+
}
|
|
917
|
+
catch (e) {
|
|
918
|
+
console.warn('Error removing route/marker:', e);
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
reAddLayers() {
|
|
923
|
+
this.activeRoutesRegistry.forEach((data, regId) => {
|
|
924
|
+
// Use the stored raw index instead of the composite registry key
|
|
925
|
+
this.drawLine(data.cordinates, data.index, data.route, false, data.routeType, true);
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
findMarkerBound(index) {
|
|
929
|
+
const id = String(index);
|
|
930
|
+
const originMarker = this.markerOriginList.get(id);
|
|
931
|
+
const originLng = originMarker?.getLngLat()?.lng;
|
|
932
|
+
const originLat = originMarker?.getLngLat()?.lat;
|
|
933
|
+
if (originLng !== undefined && originLat !== undefined) {
|
|
934
|
+
const indexOfCordinates = this.originDestinationCordinates.findIndex((x) => x[0][0].toFixed(6) === originLng.toFixed(6) &&
|
|
935
|
+
x[0][1].toFixed(6) === originLat.toFixed(6));
|
|
936
|
+
if (indexOfCordinates >= 0) {
|
|
937
|
+
this.originDestinationCordinates.splice(indexOfCordinates, 1);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async filterRoute(ID, visibility, showAllFitbound) {
|
|
942
|
+
if (ID && this.map) {
|
|
943
|
+
if (this.map.getLayer(`route-for-job-code${ID}`)) {
|
|
944
|
+
this.map.setLayoutProperty(`route-for-job-code${ID}`, 'visibility', visibility);
|
|
945
|
+
const id = String(ID);
|
|
946
|
+
const originM = this.markerOriginList.get(id);
|
|
947
|
+
if (originM) {
|
|
948
|
+
originM.getElement().style.display = visibility === 'visible' ? 'block' : 'none';
|
|
949
|
+
}
|
|
950
|
+
const destinationM = this.markerDestinationList.get(id);
|
|
951
|
+
if (destinationM) {
|
|
952
|
+
destinationM.getElement().style.display = visibility === 'visible' ? 'block' : 'none';
|
|
953
|
+
}
|
|
954
|
+
if (this.activeArcs[ID]) {
|
|
955
|
+
this.activeArcs[ID].pathEl.style.display = visibility === 'visible' ? 'block' : 'none';
|
|
956
|
+
}
|
|
957
|
+
if (visibility === 'none' && showAllFitbound) {
|
|
958
|
+
this.findMarkerBound(ID);
|
|
959
|
+
this.extendReBound();
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
if (this.map.getLayer(`arc-layer${ID}`)) {
|
|
963
|
+
this.map.setLayoutProperty(`arc-layer${ID}`, 'visibility', visibility);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return true;
|
|
967
|
+
}
|
|
968
|
+
extendBound(route, showAllFitbound) {
|
|
969
|
+
return new Promise((resolve) => {
|
|
970
|
+
if (route && this.map) {
|
|
971
|
+
if (typeof route === 'string') {
|
|
972
|
+
let path = route.split(';');
|
|
973
|
+
path = path.map((ele) => this.formateLatLong(ele));
|
|
974
|
+
path = path.filter((ele) => ele && ele.length > 1);
|
|
975
|
+
route = path;
|
|
976
|
+
}
|
|
977
|
+
if (route[0] && route[0][0] && route[0][1] && route[route.length - 1] && route[route.length - 1][0] && route[route.length - 1][1]) {
|
|
978
|
+
this.originDestinationCordinates.push(route);
|
|
979
|
+
this.bounds = new mapboxgl.LngLatBounds();
|
|
980
|
+
this.originDestinationCordinates.forEach((routeItem) => {
|
|
981
|
+
routeItem.forEach((coord) => {
|
|
982
|
+
this.bounds.extend(coord);
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (showAllFitbound && this.map) {
|
|
988
|
+
setTimeout(() => {
|
|
989
|
+
if (showAllFitbound && !this.bounds.isEmpty()) {
|
|
990
|
+
this.map.fitBounds(this.bounds, { pitch: 65, maxZoom: 14, padding: 90 }, { fitboundComplete: true });
|
|
991
|
+
}
|
|
992
|
+
}, 100);
|
|
993
|
+
this.map.once('moveend', (event) => {
|
|
994
|
+
if (event.fitboundComplete) {
|
|
995
|
+
resolve(true);
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
else {
|
|
1000
|
+
resolve(true);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
extendReBound(bottom) {
|
|
1005
|
+
return new Promise((resolve) => {
|
|
1006
|
+
this.bounds = new mapboxgl.LngLatBounds();
|
|
1007
|
+
if (this.originDestinationCordinates.length > 0) {
|
|
1008
|
+
this.originDestinationCordinates.forEach((item, index) => {
|
|
1009
|
+
item.forEach((route) => {
|
|
1010
|
+
this.bounds.extend(route);
|
|
1011
|
+
});
|
|
1012
|
+
if (index === this.originDestinationCordinates.length - 1) {
|
|
1013
|
+
const padTop = Math.max(0, (this.padding?.top || 0) + this.customTopForCustomer + 80);
|
|
1014
|
+
const padBot = Math.max(0, (this.padding?.bottom || 0) + (this.windowActualHeightWidth.availHeight - (window.innerHeight - 65)) + 80);
|
|
1015
|
+
const padLeft = Math.max(0, (this.padding?.left || 0) + 350);
|
|
1016
|
+
const padRight = Math.max(0, (this.padding?.right || 0) + 80);
|
|
1017
|
+
const customPadding = {
|
|
1018
|
+
top: padTop, bottom: padBot,
|
|
1019
|
+
left: padLeft, right: padRight,
|
|
1020
|
+
};
|
|
1021
|
+
setTimeout(() => {
|
|
1022
|
+
if (this.originDestinationCordinates.length > 0 && !this.bounds.isEmpty() && this.map) {
|
|
1023
|
+
this.map.fitBounds(this.bounds, { padding: customPadding, pitch: 65, maxZoom: 14 });
|
|
1024
|
+
}
|
|
1025
|
+
}, 500);
|
|
1026
|
+
resolve(true);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
resolve(true);
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
async plotRoute(route, i, type, enablefitbound, showAllFitbound) {
|
|
1036
|
+
const cacheKey = route['job_id'] || route['route_id'];
|
|
1037
|
+
// 1. Check cache FIRST
|
|
1038
|
+
if (this.pathCache.has(cacheKey)) {
|
|
1039
|
+
const path = this.pathCache.get(cacheKey);
|
|
1040
|
+
route['path'] = path;
|
|
1041
|
+
this.extendBound(path, showAllFitbound);
|
|
1042
|
+
await this.drawLine(path, i, route, enablefitbound, type);
|
|
1043
|
+
route['index'] = i;
|
|
1044
|
+
return true;
|
|
1045
|
+
}
|
|
1046
|
+
// 2. Check for inline path (Avoid API if already available)
|
|
1047
|
+
if (route['path'] && (Array.isArray(route['path']) || typeof route['path'] === 'string')) {
|
|
1048
|
+
let path = typeof route['path'] === 'string' ? route['path'].split(';') : route['path'];
|
|
1049
|
+
if (Array.isArray(path) && path.length > 0) {
|
|
1050
|
+
// Format if they are still strings
|
|
1051
|
+
if (typeof path[0] === 'string') {
|
|
1052
|
+
path = path.map((ele) => this.formateLatLong(ele));
|
|
1053
|
+
path = path.filter((ele) => ele && ele.length > 1);
|
|
1054
|
+
}
|
|
1055
|
+
if (path.length > 0) {
|
|
1056
|
+
this.pathCache.set(cacheKey, path);
|
|
1057
|
+
route['path'] = path;
|
|
1058
|
+
this.extendBound(path, showAllFitbound);
|
|
1059
|
+
await this.drawLine(path, i, route, enablefitbound, type);
|
|
1060
|
+
route['index'] = i;
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
// 3. Fallback to API call ONLY for jobcode (if path was missing)
|
|
1066
|
+
return new Promise((resolve, reject) => {
|
|
1067
|
+
if (['jobcode'].includes(type) || route['job_id']) {
|
|
1068
|
+
let param = { job: route['job_id'] };
|
|
1069
|
+
this.utils.postDataWithRestUrl('schedule/job/path', param).subscribe({
|
|
1070
|
+
next: async (res) => {
|
|
1071
|
+
let path = [];
|
|
1072
|
+
if (res['data'] && res['data']['route']) {
|
|
1073
|
+
path = res['data']['route'].split(';');
|
|
1074
|
+
path = path.map((ele) => this.formateLatLong(ele));
|
|
1075
|
+
path = path.filter((ele) => ele && ele.length > 1);
|
|
1076
|
+
this.pathCache.set(cacheKey, path);
|
|
1077
|
+
}
|
|
1078
|
+
if (path && path.length > 0) {
|
|
1079
|
+
route['path'] = path;
|
|
1080
|
+
this.extendBound(route['path'], showAllFitbound);
|
|
1081
|
+
await this.drawLine(route['path'], i, route, enablefitbound, type);
|
|
1082
|
+
route['index'] = i;
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
this.extendBound(null, showAllFitbound);
|
|
1086
|
+
}
|
|
1087
|
+
resolve(true);
|
|
1088
|
+
},
|
|
1089
|
+
error: (err) => {
|
|
1090
|
+
console.warn('Failed path retrieval', err);
|
|
1091
|
+
reject(false);
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
this.extendBound(null, showAllFitbound);
|
|
1097
|
+
resolve(true);
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Batch toggle visibility for routes on the map.
|
|
1103
|
+
* If visibleIds is empty, shows everything in the registry.
|
|
1104
|
+
* Otherwise, shows only those in visibleIds and hides the rest.
|
|
1105
|
+
*/
|
|
1106
|
+
setRoutesVisibility(visibleIds, showAllOverride, prefix) {
|
|
1107
|
+
const showAll = showAllOverride !== undefined ? showAllOverride : (visibleIds.length === 0);
|
|
1108
|
+
this.activeRoutesRegistry.forEach((data, id) => {
|
|
1109
|
+
const registryId = String(id).trim();
|
|
1110
|
+
// If a prefix is provided, only operate on routes matching that prefix.
|
|
1111
|
+
if (prefix && !registryId.startsWith(`${prefix}-`)) {
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
const isVisible = showAll || visibleIds.includes(registryId);
|
|
1115
|
+
const visibility = isVisible ? 'visible' : 'none';
|
|
1116
|
+
const display = isVisible ? 'block' : 'none';
|
|
1117
|
+
// Toggle New Layer Format
|
|
1118
|
+
const newLayerId = `route-layer-${data.routeType}-${data.index || id}`;
|
|
1119
|
+
if (this.map.getLayer(newLayerId)) {
|
|
1120
|
+
this.map.setLayoutProperty(newLayerId, 'visibility', visibility);
|
|
1121
|
+
}
|
|
1122
|
+
// Legacy/Alternate Layer Formats
|
|
1123
|
+
const legacyId = `route-for-job-code${id}`;
|
|
1124
|
+
if (this.map.getLayer(legacyId)) {
|
|
1125
|
+
this.map.setLayoutProperty(legacyId, 'visibility', visibility);
|
|
1126
|
+
}
|
|
1127
|
+
if (this.map.getLayer(`line${id}`)) {
|
|
1128
|
+
this.map.setLayoutProperty(`line${id}`, 'visibility', visibility);
|
|
1129
|
+
}
|
|
1130
|
+
// Toggle Markers
|
|
1131
|
+
const originM = this.markerOriginList.get(registryId);
|
|
1132
|
+
if (originM)
|
|
1133
|
+
originM.getElement().style.display = display;
|
|
1134
|
+
const destM = this.markerDestinationList.get(registryId);
|
|
1135
|
+
if (destM)
|
|
1136
|
+
destM.getElement().style.display = display;
|
|
1137
|
+
// Toggle Arcs
|
|
1138
|
+
if (this.activeArcs[registryId]) {
|
|
1139
|
+
this.activeArcs[registryId].pathEl.style.display = display;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
// Recalculate bounds based on visible routes
|
|
1143
|
+
this.recalculateVisibleBounds(visibleIds);
|
|
1144
|
+
}
|
|
1145
|
+
recalculateVisibleBounds(visibleIds) {
|
|
1146
|
+
const showAll = visibleIds.length === 0;
|
|
1147
|
+
this.bounds = new mapboxgl.LngLatBounds();
|
|
1148
|
+
this.originDestinationCordinates = [];
|
|
1149
|
+
const normalizedVisibleIds = visibleIds.map(vid => String(vid).trim());
|
|
1150
|
+
this.activeRoutesRegistry.forEach((data, id) => {
|
|
1151
|
+
const registryId = String(id).trim();
|
|
1152
|
+
if (showAll || normalizedVisibleIds.includes(registryId)) {
|
|
1153
|
+
if (data.bounds && !data.bounds.isEmpty()) {
|
|
1154
|
+
this.bounds.extend(data.bounds);
|
|
1155
|
+
this.originDestinationCordinates.push(data.cordinates);
|
|
1156
|
+
}
|
|
1157
|
+
else if (data.cordinates && data.cordinates.length > 0) {
|
|
1158
|
+
// Fallback if bounds weren't pre-calculated
|
|
1159
|
+
data.cordinates.forEach((coord) => this.bounds.extend(coord));
|
|
1160
|
+
this.originDestinationCordinates.push(data.cordinates);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
if (!this.bounds.isEmpty() && this.map) {
|
|
1165
|
+
const canvas = this.map.getCanvas();
|
|
1166
|
+
const mapW = canvas.clientWidth;
|
|
1167
|
+
const mapH = canvas.clientHeight;
|
|
1168
|
+
let padTop = Math.max(0, (Number(this.padding?.top) || 0) + (Number(this.customTopForCustomer) || 0) + 80);
|
|
1169
|
+
let padBot = Math.max(0, (Number(this.padding?.bottom) || 0) + (Math.max(0, Number(this.windowActualHeightWidth.availHeight) - (Number(window.innerHeight) - 65))) + 80);
|
|
1170
|
+
let padLeft = Math.max(0, (Number(this.padding?.left) || 0) + (window.innerWidth > 1000 ? 350 : 50));
|
|
1171
|
+
let padRight = Math.max(0, (Number(this.padding?.right) || 0) + 80);
|
|
1172
|
+
// Safety Clamps: Ensure padding doesn't exceed canvas size
|
|
1173
|
+
const maxHorizPad = mapW * 0.8;
|
|
1174
|
+
const maxVertPad = mapH * 0.8;
|
|
1175
|
+
if (padLeft + padRight > maxHorizPad) {
|
|
1176
|
+
const factor = maxHorizPad / (padLeft + padRight);
|
|
1177
|
+
padLeft *= factor;
|
|
1178
|
+
padRight *= factor;
|
|
1179
|
+
}
|
|
1180
|
+
if (padTop + padBot > maxVertPad) {
|
|
1181
|
+
const factor = maxVertPad / (padTop + padBot);
|
|
1182
|
+
padTop *= factor;
|
|
1183
|
+
padBot *= factor;
|
|
1184
|
+
}
|
|
1185
|
+
try {
|
|
1186
|
+
this.map.fitBounds(this.bounds, {
|
|
1187
|
+
padding: { top: padTop, bottom: padBot, left: padLeft, right: padRight },
|
|
1188
|
+
pitch: 65,
|
|
1189
|
+
maxZoom: 12,
|
|
1190
|
+
duration: 800
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
catch (err) {
|
|
1194
|
+
console.warn('fitBounds failed, trying with default padding', err);
|
|
1195
|
+
this.map.fitBounds(this.bounds, { padding: 50, pitch: 65, maxZoom: 16 });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async clearAllRoutes() {
|
|
1200
|
+
// 1. Remove all markers
|
|
1201
|
+
this.markerOriginList.forEach(marker => marker.remove());
|
|
1202
|
+
this.markerDestinationList.forEach(marker => marker.remove());
|
|
1203
|
+
this.markerOriginList.clear();
|
|
1204
|
+
this.markerDestinationList.clear();
|
|
1205
|
+
// 2. Remove all arcs
|
|
1206
|
+
Object.keys(this.activeArcs).forEach(index => {
|
|
1207
|
+
const arc = this.activeArcs[index];
|
|
1208
|
+
if (arc.pathEl.parentNode)
|
|
1209
|
+
arc.pathEl.parentNode.removeChild(arc.pathEl);
|
|
1210
|
+
if (this.svgOverlay) {
|
|
1211
|
+
const grad = this.svgOverlay.querySelector(`defs #arc-grad-${index}`);
|
|
1212
|
+
if (grad && grad.parentNode)
|
|
1213
|
+
grad.parentNode.removeChild(grad);
|
|
1214
|
+
}
|
|
1215
|
+
});
|
|
1216
|
+
this.activeArcs = {};
|
|
1217
|
+
// 3. Remove all line layers and sources using registry metadata
|
|
1218
|
+
this.activeRoutesRegistry.forEach((data, regId) => {
|
|
1219
|
+
const type = data.routeType || 'jobcode';
|
|
1220
|
+
const idx = data.index || regId.split('-').pop();
|
|
1221
|
+
const layerId = `route-layer-${type}-${idx}`;
|
|
1222
|
+
const sourceId = `route-source-${type}-${idx}`;
|
|
1223
|
+
this.map.getLayer(layerId) ? this.map.removeLayer(layerId) : null;
|
|
1224
|
+
this.map.getSource(sourceId) ? this.map.removeSource(sourceId) : null;
|
|
1225
|
+
// Legacy fallback cleanup
|
|
1226
|
+
this.map.getLayer(`line${idx}`) ? this.map.removeLayer(`line${idx}`) : null;
|
|
1227
|
+
this.map.getSource(`line${idx}`) ? this.map.removeSource(`line${idx}`) : null;
|
|
1228
|
+
this.map.getLayer(`route-for-job-code${idx}`) ? this.map.removeLayer(`route-for-job-code${idx}`) : null;
|
|
1229
|
+
this.map.getSource(`route-source-for-job-code${idx}`) ? this.map.removeSource(`route-source-for-job-code${idx}`) : null;
|
|
1230
|
+
});
|
|
1231
|
+
// 4. Reset registries and bounds
|
|
1232
|
+
this.activeRoutesRegistry.clear();
|
|
1233
|
+
this.originDestinationCordinates = [];
|
|
1234
|
+
this.bounds = new mapboxgl.LngLatBounds();
|
|
1235
|
+
if (this.popup)
|
|
1236
|
+
this.popup.remove();
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
clearBound() {
|
|
1240
|
+
this.bounds = new mapboxgl.LngLatBounds();
|
|
1241
|
+
this.originDestinationCordinates = [];
|
|
1242
|
+
this.clearPadding();
|
|
1243
|
+
}
|
|
1244
|
+
formateLatLong(latlong) {
|
|
1245
|
+
return latlong ? latlong.split(',').map((x) => +x).reverse() : null;
|
|
1246
|
+
}
|
|
1247
|
+
clearBoundWithCordinates() {
|
|
1248
|
+
this.bounds = new mapboxgl.LngLatBounds();
|
|
1249
|
+
this.originDestinationCordinates = [];
|
|
1250
|
+
}
|
|
1251
|
+
onResize(event) {
|
|
1252
|
+
if (!this.bounds.isEmpty() && this.map) {
|
|
1253
|
+
this.windowActualHeightWidth.availHeight =
|
|
1254
|
+
window.innerHeight > window.screen.availHeight ? window.innerHeight : window.screen.availHeight;
|
|
1255
|
+
setTimeout(() => {
|
|
1256
|
+
const hRes = event.target ? event.target.innerHeight : window.innerHeight;
|
|
1257
|
+
this.map.fitBounds(this.bounds, {
|
|
1258
|
+
padding: {
|
|
1259
|
+
top: (this.padding?.top || 0) + this.customTopForCustomer,
|
|
1260
|
+
bottom: (this.padding?.bottom || 0) + (this.windowActualHeightWidth.availHeight - (hRes - 65)),
|
|
1261
|
+
left: (this.padding?.left || 0),
|
|
1262
|
+
right: (this.padding?.right || 0),
|
|
1263
|
+
},
|
|
1264
|
+
pitch: 65,
|
|
1265
|
+
});
|
|
1266
|
+
}, 500);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
setWindowHeight(screen) {
|
|
1270
|
+
this.windowActualHeightWidth.availHeight = screen;
|
|
1271
|
+
}
|
|
1272
|
+
setPadding(padding) {
|
|
1273
|
+
this.padding = padding;
|
|
1274
|
+
}
|
|
1275
|
+
clearPadding() {
|
|
1276
|
+
this.padding = null;
|
|
1277
|
+
}
|
|
1278
|
+
removeJobFromMap(data) {
|
|
1279
|
+
data.forEach((ele, index) => {
|
|
1280
|
+
const id = ele['job_id'] ? ele['job_id'] : ele['route_id'];
|
|
1281
|
+
this.removeRouteAndMarker(id);
|
|
1282
|
+
if (index === data.length - 1) {
|
|
1283
|
+
this.extendReBound();
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
resize() {
|
|
1288
|
+
if (this.map) {
|
|
1289
|
+
setTimeout(() => this.map.resize(), 0);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MapboxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1293
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MapboxService, providedIn: 'root' });
|
|
1294
|
+
}
|
|
1295
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MapboxService, decorators: [{
|
|
1296
|
+
type: Injectable,
|
|
1297
|
+
args: [{
|
|
1298
|
+
providedIn: 'root'
|
|
1299
|
+
}]
|
|
1300
|
+
}], ctorParameters: () => [] });
|
|
1301
|
+
|
|
1302
|
+
class CoolmapService {
|
|
1303
|
+
mapbox = inject(MapboxService);
|
|
1304
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1305
|
+
plottingIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "plottingIds" }] : []));
|
|
1306
|
+
plotRoute(route, i, type = 'jobcode', enablefitbound, showAllFitbound) {
|
|
1307
|
+
if (!route)
|
|
1308
|
+
return;
|
|
1309
|
+
return this.mapbox.plotRoute(route, i, type, enablefitbound, showAllFitbound);
|
|
1310
|
+
}
|
|
1311
|
+
loadMapProperty(pinRouteGeojson, index, unit, routeProps) {
|
|
1312
|
+
this.mapbox.loadMapProperty(pinRouteGeojson, index, unit, routeProps);
|
|
1313
|
+
}
|
|
1314
|
+
removeRouteAndMarker(index, type = 'jobcode') {
|
|
1315
|
+
return this.mapbox.removeRouteAndMarker(index, type);
|
|
1316
|
+
}
|
|
1317
|
+
clearAllRoutes() {
|
|
1318
|
+
return this.mapbox.clearAllRoutes();
|
|
1319
|
+
}
|
|
1320
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1321
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapService, providedIn: 'root' });
|
|
1322
|
+
}
|
|
1323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: CoolmapService, decorators: [{
|
|
1324
|
+
type: Injectable,
|
|
1325
|
+
args: [{
|
|
1326
|
+
providedIn: 'root'
|
|
1327
|
+
}]
|
|
1328
|
+
}] });
|
|
1329
|
+
|
|
1330
|
+
/*
|
|
1331
|
+
* Public API Surface of coolmap-services
|
|
1332
|
+
*/
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Generated bundle index. Do not edit.
|
|
1336
|
+
*/
|
|
1337
|
+
|
|
1338
|
+
export { COOLMAP_CONFIG, CoolmapService, CoolmapServices, MapboxService, UtilsService, provideCoolmapConfig };
|
|
1339
|
+
//# sourceMappingURL=coolmap-services.mjs.map
|