@sapui5/sap.ui.vbm 1.145.0 → 1.146.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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/sap/ui/vbm/.library +1 -1
  3. package/src/sap/ui/vbm/Adapter.js +4 -4
  4. package/src/sap/ui/vbm/Adapter3D.js +1 -1
  5. package/src/sap/ui/vbm/Viewport.js +1 -1
  6. package/src/sap/ui/vbm/adapter3d/ColladaBounds.js +1 -1
  7. package/src/sap/ui/vbm/adapter3d/DragDropHandler.js +1 -1
  8. package/src/sap/ui/vbm/adapter3d/ModelHandler.js +1 -1
  9. package/src/sap/ui/vbm/adapter3d/ObjectFactory.js +1 -1
  10. package/src/sap/ui/vbm/adapter3d/PolygonHandler.js +1 -1
  11. package/src/sap/ui/vbm/adapter3d/RectangleTracker.js +1 -1
  12. package/src/sap/ui/vbm/adapter3d/SceneBuilder.js +1 -1
  13. package/src/sap/ui/vbm/adapter3d/VBIJSONParser.js +1 -1
  14. package/src/sap/ui/vbm/library.js +2 -2
  15. package/src/sap/ui/vbm/themes/base/AnalyticMap.less +5 -5
  16. package/src/sap/ui/vbm/themes/base/Cluster.less +41 -48
  17. package/src/sap/ui/vbm/themes/base/ContainerBase.less +14 -14
  18. package/src/sap/ui/vbm/themes/base/ContainerLegendItem.less +12 -27
  19. package/src/sap/ui/vbm/themes/base/DetailWindow.less +12 -8
  20. package/src/sap/ui/vbm/themes/base/ListPanel.less +17 -18
  21. package/src/sap/ui/vbm/themes/base/ListPanelStack.less +9 -10
  22. package/src/sap/ui/vbm/themes/base/MapContainer.less +49 -41
  23. package/src/sap/ui/vbm/themes/base/VBI.less +356 -491
  24. package/src/sap/ui/vbm/themes/base/Viewport.less +5 -1
  25. package/src/sap/ui/vbm/themes/base/library.source.less +1 -2
  26. package/src/sap/ui/vbm/themes/sap_fiori_3_dark/VBI.less +46 -55
  27. package/src/sap/ui/vbm/themes/sap_fiori_3_dark/library.source.less +1 -2
  28. package/src/sap/ui/vbm/themes/sap_fiori_3_hcb/VBI.less +46 -55
  29. package/src/sap/ui/vbm/themes/sap_fiori_3_hcb/library.source.less +1 -2
  30. package/src/sap/ui/vbm/themes/sap_horizon_dark/VBI.less +45 -54
  31. package/src/sap/ui/vbm/themes/sap_horizon_dark/library.source.less +1 -2
  32. package/src/sap/ui/vbm/themes/sap_horizon_hcb/VBI.less +45 -54
  33. package/src/sap/ui/vbm/themes/sap_horizon_hcb/library.source.less +1 -3
  34. package/src/sap/ui/vbm/themes/sap_horizon_hcw/VBI.less +45 -54
  35. package/src/sap/ui/vbm/themes/sap_horizon_hcw/library.source.less +1 -2
  36. package/src/sap/ui/vbm/vector/MapRenderer.js +497 -416
  37. package/src/sap/ui/vbm/vector/PayloadGenerator.js +1 -1
  38. package/src/sap/ui/vbm/vector/RectangularSelection.js +1 -1
  39. package/src/sap/ui/vbm/vector/VBITransformer.js +19 -4
  40. package/src/sap/ui/vbm/themes/base/GeoMap.less +0 -8
  41. package/src/sap/ui/vbm/themes/sap_fiori_3_dark/AnalyticMap.less +0 -4
  42. package/src/sap/ui/vbm/themes/sap_fiori_3_hcb/AnalyticMap.less +0 -4
  43. package/src/sap/ui/vbm/themes/sap_horizon_dark/AnalyticMap.less +0 -3
  44. package/src/sap/ui/vbm/themes/sap_horizon_hcb/AnalyticMap.less +0 -3
  45. package/src/sap/ui/vbm/themes/sap_horizon_hcw/AnalyticMap.less +0 -3
@@ -12,21 +12,14 @@ sap.ui.define([
12
12
  "./SAPNavControl",
13
13
  "./SAPAutomationManager",
14
14
  "sap/ui/core/Lib",
15
- //"../lib/sapscene",
16
15
  "./thirdparty/maplibregl",
17
16
  "./VBITransformer"
18
- ], function (vb, visualobjects, actions, VBIRenderer, VectorUtils, MaplibreStyles, PayloadGenerator, RectangularSelection, LassoSelection, SAPMapNavControl, SAPAutomationManager, Lib) {
17
+ ], function (vb, visualobjects, actions, VBIRenderer, VectorUtils, MaplibreStyles, PayloadGenerator, RectangularSelection, LassoSelection, SAPMapNavControl, SAPAutomationManager, Lib) {
19
18
  'use strict';
20
19
  visualobjects = {};
21
20
  VBI.MapRenderer = {};
22
21
  actions = {};
23
22
  var map_container = "";
24
- //let isDragging = false;
25
- //let startY;
26
- //let startYpx;
27
- // let isDrawing = false;
28
- // let isCtrlPressed = false;
29
- // let lassoPoints = [];
30
23
  let predefinedMarkers = [];
31
24
  let allMarkers = [];
32
25
  let lineDrag = false;
@@ -35,12 +28,15 @@ sap.ui.define([
35
28
  let dropItems = {};
36
29
  let dragInstance = {};
37
30
  VBI.MapRenderer.name = undefined;
31
+ VBI.MapRenderer.allMarkers = undefined;
38
32
  let spotid = 0;
39
33
  let fixedBounds;
40
34
  var containerID;
41
- let previousCenter = null;
42
- let previousZoom = null;
43
-
35
+ let geoJSON;
36
+ var map;
37
+ let mapCanvas;
38
+ const renderedSpots = new Map();
39
+ const renderedRouteLabels = new Map();
44
40
 
45
41
 
46
42
  VBI.MapRenderer.setAdapter = (adapter) => {
@@ -48,120 +44,147 @@ sap.ui.define([
48
44
  this._payloadGenerator = new PayloadGenerator(this._adapter);
49
45
  }
50
46
 
51
- // Process the GeoJSON spots and its properties
52
- VBI.MapRenderer._processGeoSpot = (source, data) => {
47
+ VBI.MapRenderer.renderMap = () => {
48
+
49
+ // if (this.map) {
50
+ // previousCenter = this.map.getCenter();
51
+ // previousZoom = this.map.getZoom();
52
+ // this.map.remove();
53
+ // }
54
+
55
+ geoJSON = VBI.VBITransformer.getTransformedJSON();
56
+
57
+ map_container = VBIRenderer.getId();
58
+
59
+ var styleSheet = document.createElement("style");
60
+
61
+ styleSheet.textContent = MaplibreStyles.loadStyles();
62
+ document.head.appendChild(styleSheet);
63
+ if (!window._maplibreMap) {
64
+ window._maplibreMap = VectorUtils.createMap(geoJSON, map_container);
65
+ }
66
+ map = window._maplibreMap;
67
+ const canvas = map.getCanvasContainer();
68
+ const rectangularSelection = new RectangularSelection(map);
69
+ const lassoSelection = new LassoSelection(map);
70
+ const popupLink = new maplibregl.Popup({
71
+ closeButton: false,
72
+ closeOnClick: false
73
+ });
74
+ // Set `true` to dispatch the event before other functions
75
+ // call it. This is necessary for disabling the default map
76
+ // dragging behaviour.
77
+ canvas.addEventListener('mousedown', (e) => rectangularSelection.mouseDown(e, this.Rpressed), true);
78
+ canvas.addEventListener('mousedown', (e) => lassoSelection.mouseDown(e, this.Apressed), true);
79
+ map.touchZoomRotate.enable();
80
+ mapCanvas = map.getCanvas();
81
+ mapCanvas.id = VectorUtils._setcanvasid();
82
+ mapCanvas.oncontextmenu = (e) => {
83
+ e.preventDefault();
84
+ // Get the mouse coordinates within the map container
85
+ const rect = canvas.getBoundingClientRect(); // Get map container position
86
+ const x = e.clientX - rect.left; // Relative to map
87
+ const y = e.clientY - rect.top;
88
+ const lngLat = map.unproject([x, y]);
89
+ // Check if the right-click happened on the 'geojson-source-route' layer
90
+ const features = map.queryRenderedFeatures([x, y], { layers: ['geojson-source-route'] });
53
91
 
92
+ if (features.length > 0) {
93
+ e.features = features;
94
+ // The context menu is for the 'geojson-source-route' layer
95
+ triggerPayloadRoute(e, 'CONTEXT_MENU_REQUEST');
96
+ } else {
97
+ // Get the mouse coordinates within the map container
98
+ const offsetX = e.offsetX;
99
+ const offsetY = e.offsetY;
100
+ const lngLat = map.unproject([offsetX, offsetY]);
101
+ // The context menu is for the map
102
+ const coords = lngLat.lng + ";" + lngLat.lat + ";0.0";
103
+ const currentZoom = map.getZoom();
104
+ const center = map.getCenter();
105
+ const currentCenter = center.lng + ";" + center.lat;
106
+ const xyobj = VectorUtils.GetEventVPCoordsObj(e, map_container);
107
+ const screenX = xyobj.x;
108
+ const screenY = xyobj.y;
109
+ PayloadGenerator.onMapContextMenu(coords, currentZoom, currentCenter, screenX, screenY);
110
+ }
111
+ };
112
+
113
+ map.on('load', () => {
114
+
115
+ map.getCanvas().style.cursor = 'default';
116
+ map.on('mouseup', function () {
117
+ map.getCanvas().style.cursor = 'default';
118
+ });
119
+ map.getCanvas().addEventListener('mousemove', function () {
120
+ map.getCanvas().style.cursor = 'default';
121
+ });
122
+
123
+ if (VBI.mapFlags.isLegendExists) {
124
+ // Legend control
125
+ VBI.VBITransformer._createLegend(map_container);
126
+ }
127
+ // Custom attribution/copyright control
128
+ map.addControl(new maplibregl.AttributionControl({
129
+ customAttribution: '<span>' + geoJSON[0].copyright + '</span>',
130
+ compact: false
131
+ })
132
+ );
133
+ //validate scale visiblity
134
+ var scaleControl = new maplibregl.ScaleControl({
135
+ maxWidth: 80,
136
+ unit: geoJSON[0].scaleType
137
+ })
138
+ if (VBI.mapFlags.scaleVisible) {
139
+ // Scale control in mi,km or nm
140
+ map.addControl(scaleControl);
141
+ } else if (!VBI.mapFlags.scaleVisible && map.hasControl(scaleControl)) {
142
+ map.removeControl(scaleControl);
143
+ }
144
+
145
+ // Parsing GeoJSON for each type of object
146
+
147
+ map.addSource('geojson-source', {
148
+ 'type': 'geojson',
149
+ 'data': geoJSON[1]
150
+ });
151
+
152
+ let sapNavControl = new SAPMapNavControl(VBI.mapFlags.moveDisable, VBI.mapFlags.zoomDisable);
153
+ if (VBI.mapFlags.navControlVisible) {
154
+ map.addControl(sapNavControl, 'top-left');
155
+ } else if (!VBI.mapFlags.navControlVisible && map.hasControl(sapNavControl)) {
156
+ map.removeControl(sapNavControl);
157
+ }
158
+ // Create a popup, but don't add it to the map yet.
159
+ const popup = new maplibregl.Popup({
160
+ closeButton: false,
161
+ closeOnClick: false,
162
+ offset: {
163
+ 'top': [0, 0],
164
+ 'bottom': [0, -25]
165
+ }
166
+ });
167
+ });
54
168
  }
55
169
 
56
- // Process the GeoJSON routes/links and its properties
57
- VBI.MapRenderer._processGeoRoutes = (source, data) => {
58
-
170
+ VBI.MapRenderer._processAutomation = () => {
171
+  let automations = new SAPAutomationManager(map, maplibregl.LngLatBounds);
172
+         automations.load(VBI.mapFlags.automations);
59
173
  }
60
174
 
61
- VBI.MapRenderer.renderMap = () => {
62
-
63
- if (this.map) {
64
- previousCenter = this.map.getCenter();
65
- previousZoom = this.map.getZoom();
66
- this.map.remove();
67
- }
175
+ VBI.MapRenderer._processGeoSpot = () => {
68
176
 
69
- let geoJSON = VBI.VBITransformer.getTransformedJSON();
70
-
71
- map_container = VBIRenderer.getId();
72
-
73
- var styleSheet = document.createElement("style");
74
-
75
- styleSheet.textContent = MaplibreStyles.loadStyles();
76
- document.head.appendChild(styleSheet);
77
-
78
- const map = VectorUtils.createMap(geoJSON, map_container);
79
- map.doubleClickZoom.disable();
80
-
81
- if (previousCenter && previousZoom !== null) {
82
- map.jumpTo({
83
- center: [previousCenter.lng, previousCenter.lat],
84
- zoom: previousZoom
85
-
86
- });
87
- }
88
-
89
- const canvas = map.getCanvasContainer();
90
- const rectangularSelection = new RectangularSelection(map);
91
- const lassoSelection = new LassoSelection(map);
92
- const mapCanvas = map.getCanvas();
93
- const popupLink = new maplibregl.Popup({
94
- closeButton: false,
95
- closeOnClick: false
96
- });
97
- mapCanvas.id = VectorUtils._setcanvasid();
98
- // Set `true` to dispatch the event before other functions
99
- // call it. This is necessary for disabling the default map
100
- // dragging behaviour.
101
- canvas.addEventListener('mousedown', (e) => rectangularSelection.mouseDown(e, this.Rpressed), true);
102
- canvas.addEventListener('mousedown', (e) => lassoSelection.mouseDown(e, this.Apressed), true);
103
- map.touchZoomRotate.enable();
104
- this.map = map;
105
- map.on('load', () => {
106
-
107
- map.getCanvas().style.cursor = 'default';
108
- map.on('mouseup', function () {
109
- map.getCanvas().style.cursor = 'default';
110
- });
111
- map.getCanvas().addEventListener('mousemove', function () {
112
- map.getCanvas().style.cursor = 'default';
113
- });
114
-
115
- if (VBI.mapFlags.isLegendExists) {
116
- // Legend control
117
- VBI.VBITransformer._createLegend(map_container);
118
- }
119
- // Custom attribution/copyright control
120
- map.addControl(new maplibregl.AttributionControl({
121
- customAttribution: '<span>' + geoJSON[0].copyright + '</span>',
122
- compact: false
123
- })
124
- );
125
- //validate scale visiblity
126
- var scaleControl = new maplibregl.ScaleControl({
127
- maxWidth: 80,
128
- unit: geoJSON[0].scaleType
129
- })
130
- if (VBI.mapFlags.scaleVisible) {
131
- // Scale control in mi,km or nm
132
- map.addControl(scaleControl);
133
- } else if (!VBI.mapFlags.scaleVisible && map.hasControl(scaleControl)) {
134
- map.removeControl(scaleControl);
135
- }
136
-
137
- // Parsing GeoJSON for each type of object
138
- map.addSource('geojson-source', {
139
- 'type': 'geojson',
140
- 'data': geoJSON[1]
141
- });
142
-
143
- let sapNavControl = new SAPMapNavControl(VBI.mapFlags.moveDisable, VBI.mapFlags.zoomDisable);
144
- if (VBI.mapFlags.navControlVisible) {
145
- map.addControl(sapNavControl, 'top-left');
146
- } else if (!VBI.mapFlags.navControlVisible && map.hasControl(sapNavControl)) {
147
- map.removeControl(sapNavControl);
148
- }
149
-
150
- // map.addControl(new SAPMAPLgndControl(), 'top-right');
151
- // Create a popup, but don't add it to the map yet.
152
- const popup = new maplibregl.Popup({
153
- closeButton: false,
154
- closeOnClick: false,
155
- offset: {
156
- 'top': [0, 0],
157
- 'bottom': [0, -25]
158
- }
159
- });
160
177
  // add markers to map only for Points
161
178
  const pointFeatures = [];
179
+ geoJSON = VBI.VBITransformer.getTransformedJSON();
162
180
  geoJSON[1].features.forEach((marker) => {
163
181
  let markerCoordinates = marker.geometry.coordinates;
164
182
  if (marker.geometry.type === 'Point') {
183
+ const key = marker.properties.Key;
184
+ // Skip if this spot already exists
185
+ if (renderedSpots.has(key)) {
186
+ return;
187
+ }
165
188
  // create a DOM element for the marker (parent div)
166
189
  let createdSpot = VectorUtils.createSpotElement(marker, spotid);
167
190
  const el = createdSpot.spotEl;
@@ -178,107 +201,17 @@ sap.ui.define([
178
201
  // Append the icon inside the marker
179
202
  el.appendChild(child_el);
180
203
  }
181
- // add marker to map
182
- let spot = new maplibregl.Marker({
183
- element: el,
184
- draggable: true,
185
- anchor: 'bottom'
186
- }).setLngLat(marker.geometry.coordinates)
187
- .on('dragend', onDragEnd)
188
- .addTo(map);
189
- let originalpos = spot.getLngLat();
190
- spot.customProperties = { Key: marker.properties.Key };
191
- allMarkers.push(spot);
192
- function calculateBBox(featureCollection) {
193
- let minLon = Infinity, minLat = Infinity;
194
- let maxLon = -Infinity, maxLat = -Infinity;
195
- featureCollection.features.forEach(feature => {
196
- const featureCoordsStr = feature.properties.GeoPosition;
197
- const featureCoords = featureCoordsStr.split(";").slice(0, 2).map(Number);
198
- const lon = featureCoords[0];
199
- const lat = featureCoords[1];
200
- if (lon < minLon) minLon = lon;
201
- if (lon > maxLon) maxLon = lon;
202
- if (lat < minLat) minLat = lat;
203
- if (lat > maxLat) maxLat = lat;
204
- });
205
- return { minLon, minLat, maxLon, maxLat };
206
- }
207
- function isSpotInsideBBox(pos, bbox, tolerance = 0.05) {
208
- const [lon, lat] = [parseFloat(pos.lng), parseFloat(pos.lat)];
209
- return (
210
- lon >= bbox.minLon - tolerance &&
211
- lon <= bbox.maxLon + tolerance &&
212
- lat >= bbox.minLat - tolerance &&
213
- lat <= bbox.maxLat + tolerance
214
- );
215
- }
216
- function findDropSpots(pos, featureCollection, tolerance = 0.05) {
217
- let bbox = calculateBBox(featureCollection);
218
-
219
- if (!isSpotInsideBBox(pos, bbox, tolerance = 0.05)) {
220
- return null;
221
- }
222
-
223
- let dropSpots = featureCollection.features.filter(feature => {
224
- const featureCoordsStr = feature.properties.GeoPosition;
225
- const featureCoords = featureCoordsStr.split(";").slice(0, 2).map(Number);
226
- return isSpotInsideBBox({ lng: featureCoords[0], lat: featureCoords[1] }, bbox);
227
- });
228
- return dropSpots.length > 0 ? dropSpots : null;
229
- }
230
- VBI.MapRenderer.findNearestSpot = (pos, features1) => {
231
- let dropInstance = null;
232
- let minDistance = 100000;
233
- if (!Array.isArray(pos)) {
234
- pos = [parseFloat(pos.lng), parseFloat(pos.lat)];
235
- }
236
-
237
- features1.forEach(feature => {
238
-
239
- const featureCoordsStr = feature.properties.GeoPosition;
240
- const featureCoords = featureCoordsStr.split(";").slice(0, 2).map(Number);
241
-
242
- let distance = VBI.MapRenderer.haversineDistance(pos, featureCoords);
243
-
244
- // Check if this is the nearest spot
245
- if (distance < minDistance) {
246
- minDistance = distance;
247
- dropInstance = feature;
248
- }
204
+ const onDragEnd = (e) => {
205
+ map.getCanvas().style.cursor = 'default';
206
+ let dragItems = [];
207
+ let dropItems = [];
208
+ const key = e.target.customProperties;
209
+ const pos = e.target._lngLat;
210
+ try {
211
+ // Query all features from the layer "geojson-source-point"
212
+ const features1 = map.queryRenderedFeatures({
213
+ layers: ['geojson-source-point']
249
214
  });
250
- return dropInstance;
251
- }
252
- VBI.MapRenderer.haversineDistance = (pos, featureCoords) => {
253
- const R = 6371000;
254
- const lat1 = pos[0]
255
- const lon1 = pos[1]
256
- const lat2 = featureCoords[0]
257
- const lon2 = featureCoords[1];
258
- const dLat = VBI.MapRenderer.toRad(lat2 - lat1);
259
- const dLon = VBI.MapRenderer.toRad(lon2 - lon1);
260
- const a =
261
- Math.sin(dLat / 2) * Math.sin(dLat / 2) +
262
- Math.cos(VBI.MapRenderer.toRad(lat1)) * Math.cos(VBI.MapRenderer.toRad(lat2)) *
263
- Math.sin(dLon / 2) * Math.sin(dLon / 2);
264
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
265
- return R * c;
266
- }
267
- // Helper function to convert degrees to radians
268
- VBI.MapRenderer.toRad = (value) => {
269
- return value * Math.PI / 180;
270
- }
271
- function onDragEnd(e) {
272
- map.getCanvas().style.cursor = 'default';
273
- let dragItems = [];
274
- let dropItems = [];
275
- const key = e.target.customProperties;
276
- const pos = e.target._lngLat;
277
- try {
278
- // Query all features from the layer "geojson-source-point"
279
- const features1 = map.queryRenderedFeatures({
280
- layers: ['geojson-source-point']
281
- });
282
215
 
283
216
  features1.forEach((feature, index) => {
284
217
  if (feature.properties && typeof feature.properties.Key !== 'undefined') {
@@ -366,18 +299,118 @@ sap.ui.define([
366
299
  var dragInsId = VBI.VBITransformer.findObject(dragInstance.properties.type);
367
300
  var dropInsId = VBI.VBITransformer.findObject(dropInstance.properties.type);
368
301
 
369
- PayloadGenerator.triggerPayloaddnd(dragInsId, dropInsId, dragInstance, dropInstance, containerID);
370
- }
371
- } catch (error) {
372
- console.error('Error in onDragEnd:', error);
373
- }
374
- // Reset the dragged spot position if needed
375
- const lngLat = spot.getLngLat();
376
- if (lngLat.lng !== 0 && lngLat.lat !== 0) {
377
- spot.setLngLat(originalpos);
302
+ PayloadGenerator.triggerPayloaddnd(dragInsId, dropInsId, dragInstance, dropInstance, containerID);
378
303
  }
379
- map.getCanvas().style.cursor = 'default';
304
+ } catch (error) {
305
+ console.error('Error in onDragEnd:', error);
306
+ }
307
+ // Reset the dragged spot position if needed
308
+ const lngLat = spot.getLngLat();
309
+ if (lngLat.lng !== 0 && lngLat.lat !== 0) {
310
+ spot.setLngLat(originalpos);
380
311
  }
312
+ map.getCanvas().style.cursor = 'default';
313
+ }
314
+ // add marker to map
315
+ let spot = new maplibregl.Marker({
316
+ element: el,
317
+ draggable: true,
318
+ anchor: 'bottom'
319
+ }).setLngLat(marker.geometry.coordinates)
320
+ .on('dragend', onDragEnd)
321
+ .addTo(map);
322
+ let originalpos = spot.getLngLat();
323
+ spot.customProperties = { Key: marker.properties.Key };
324
+ allMarkers.push(spot);
325
+ VBI.MapRenderer.allMarkers = allMarkers;
326
+ function calculateBBox(featureCollection) {
327
+ let minLon = Infinity, minLat = Infinity;
328
+ let maxLon = -Infinity, maxLat = -Infinity;
329
+ featureCollection.features.forEach(feature => {
330
+ const featureCoordsStr = feature.properties.GeoPosition;
331
+ const featureCoords = featureCoordsStr.split(";").slice(0, 2).map(Number);
332
+ const lon = featureCoords[0];
333
+ const lat = featureCoords[1];
334
+ if (lon < minLon) minLon = lon;
335
+ if (lon > maxLon) maxLon = lon;
336
+ if (lat < minLat) minLat = lat;
337
+ if (lat > maxLat) maxLat = lat;
338
+ });
339
+ return { minLon, minLat, maxLon, maxLat };
340
+ }
341
+ function isSpotInsideBBox(pos, bbox, tolerance = 0.05) {
342
+ const [lon, lat] = [parseFloat(pos.lng), parseFloat(pos.lat)];
343
+ return (
344
+ lon >= bbox.minLon - tolerance &&
345
+ lon <= bbox.maxLon + tolerance &&
346
+ lat >= bbox.minLat - tolerance &&
347
+ lat <= bbox.maxLat + tolerance
348
+ );
349
+ }
350
+ function findDropSpots(pos, featureCollection, tolerance = 0.05) {
351
+ let bbox = calculateBBox(featureCollection);
352
+
353
+ // if (!isSpotInsideBBox(pos, bbox, tolerance = 0.05)) {
354
+ // return null;
355
+ // }
356
+
357
+ // let dropSpots = featureCollection.features.filter(feature => {
358
+ // const featureCoordsStr = feature.properties.GeoPosition;
359
+ // const featureCoords = featureCoordsStr.split(";").slice(0, 2).map(Number);
360
+ // return isSpotInsideBBox({ lng: featureCoords[0], lat: featureCoords[1] }, bbox);
361
+ // });
362
+ // return dropSpots.length > 0 ? dropSpots : null;
363
+ // }
364
+ VBI.MapRenderer.findNearestSpot = (pos, features1) => {
365
+ let dropInstance = null;
366
+ let minDistance = Infinity;
367
+
368
+ var zoom = window._maplibreMap.getZoom();
369
+ const tolerance = getToleranceMeters(zoom);
370
+
371
+ function getToleranceMeters(zoom) {
372
+ return Math.max(2, Math.pow(1.6, zoom - 6));
373
+ }
374
+
375
+
376
+ if (!Array.isArray(pos)) {
377
+ pos = [parseFloat(pos.lng), parseFloat(pos.lat)];
378
+ }
379
+
380
+ features1.forEach(feature => {
381
+
382
+ const featureCoordsStr = feature.properties.GeoPosition;
383
+ const featureCoords = featureCoordsStr.split(";").slice(0, 2).map(Number);
384
+
385
+ let distance = VBI.MapRenderer.haversineDistance(pos, featureCoords);
386
+
387
+ // Check if this is the nearest spot
388
+ if (distance < tolerance && distance < minDistance) {
389
+ minDistance = distance;
390
+ dropInstance = feature;
391
+ }
392
+ });
393
+ return dropInstance;
394
+ }
395
+ VBI.MapRenderer.haversineDistance = (pos, featureCoords) => {
396
+ const R = 6371000;
397
+ const lat1 = pos[0]
398
+ const lon1 = pos[1]
399
+ const lat2 = featureCoords[0]
400
+ const lon2 = featureCoords[1];
401
+ const dLat = VBI.MapRenderer.toRad(lat2 - lat1);
402
+ const dLon = VBI.MapRenderer.toRad(lon2 - lon1);
403
+ const a =
404
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
405
+ Math.cos(VBI.MapRenderer.toRad(lat1)) * Math.cos(VBI.MapRenderer.toRad(lat2)) *
406
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
407
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
408
+ return R * c;
409
+ }
410
+ // Helper function to convert degrees to radians
411
+ VBI.MapRenderer.toRad = (value) => {
412
+ return value * Math.PI / 180;
413
+ }
381
414
 
382
415
  // Function to return a promise that resolves when the map is clicked
383
416
  function getClickCoordinates() {
@@ -514,185 +547,220 @@ sap.ui.define([
514
547
  if (!exists) {
515
548
  predefinedMarkers.push(markerCoordinates);
516
549
  }
517
- } else if (marker.geometry.type == "LineString") {
518
550
 
519
- if (marker.properties.Label) {
520
- const coords = marker.geometry.coordinates;
521
- let midpoint = VectorUtils.getVisualMidpoint(coords, map);
522
-
523
- const routeLabelEl = VectorUtils.createRouteLabel(
524
- marker.properties.Label,
525
- marker.properties.LabelBGColor
526
- );
527
- const labelMarker = new maplibregl.Marker({
528
- element: routeLabelEl,
529
- anchor: 'left',
530
- offset: [10, 0]
531
- })
532
- .setLngLat(midpoint)
533
- .addTo(map);
534
-
535
- map.on('move', () => {
536
- try {
537
- const newPos = VectorUtils.getVisualMidpoint(coords, map);
538
- labelMarker.setLngLat(newPos);
539
- } catch (e) {
540
- console.warn("Label update failed:", e);
541
- }
542
- });
543
- }
544
-
545
- const coords = marker.geometry.coordinates;
546
- const startCoord = coords[0]; // First coordinate
547
- const endCoord = coords[coords.length - 1]; // Last coordinate
548
-
549
- // Calculate angle between the two points
550
- const angle = VectorUtils.calculateBearing(startCoord, endCoord);
551
-
552
- // Determine the arrow rotations
553
- const normalizedStartRotation = VectorUtils.normalizeAngle(90 + angle); // Adjust for start arrow
554
- const normalizedEndRotation = angle > 90 ? VectorUtils.normalizeAngle(270 + angle) : VectorUtils.normalizeAngle(90 - angle); // Adjust for end arrow
555
-
556
- if (coords.length > 1) {
557
- // Create start point
558
- const startPoint = {
559
- 'type': 'Feature',
560
- 'geometry': {
561
- 'type': 'Point',
562
- 'coordinates': coords[0] // First coordinate
563
- },
564
- 'properties': {
565
- 'Color': marker.properties.Color,
566
- 'BorderColor': marker.properties.BorderColor,
567
- 'arrowRotation': normalizedStartRotation,
568
- 'size': 0.07 * parseFloat(marker.properties.LineWidth),
569
- 'borderSize': 0.07 * (parseFloat(marker.properties.LineWidth) + 1)
570
- }
571
- };
572
-
573
- // Create end point
574
- const endPoint = {
575
- 'type': 'Feature',
576
- 'geometry': {
577
- 'type': 'Point',
578
- 'coordinates': coords[coords.length - 1] // Last coordinate
579
- },
580
- 'properties': {
581
- 'Color': marker.properties.Color,
582
- 'BorderColor': marker.properties.BorderColor,
583
- 'arrowRotation': normalizedEndRotation,
584
- 'size': 0.07 * parseFloat(marker.properties.LineWidth),
585
- 'borderSize': 0.07 * (parseFloat(marker.properties.LineWidth) + 1)
586
- }
587
- };
588
-
589
- // Add the points only if the arrow is supposed to be shown
590
- if (marker.properties.StartStyle === '1') {
591
- pointFeatures.push(startPoint);
592
- }
593
- if (marker.properties.EndStyle === '1') {
594
- pointFeatures.push(endPoint);
595
- }
596
- }
551
+ if (!map.getLayer('geojson-source-point')){
552
+ map.addLayer({
553
+ 'id': 'geojson-source-point',
554
+ 'type': 'circle',
555
+ 'source': 'geojson-source',
556
+ 'paint': {
557
+ 'circle-opacity': 0 // Hide points by making them fully transparent
558
+ },
559
+ 'filter': ['==', '$type', 'Point']
560
+ });
597
561
  }
598
-
599
- });
600
- if (VBI.mapFlags.automations) {
601
- let automations = new SAPAutomationManager(map, maplibregl.LngLatBounds);
602
- automations.load(VBI.mapFlags.automations);
603
562
  }
604
- map.addLayer({
605
- 'id': 'geojson-source-point',
606
- 'type': 'circle',
607
- 'source': 'geojson-source',
608
- 'paint': {
609
- 'circle-opacity': 0 // Hide points by making them fully transparent
610
- },
611
- 'filter': ['==', '$type', 'Point']
612
- });
613
-
614
- // First layer for the border (wider line)
615
- map.addLayer({
616
- 'id': 'geojson-source-route-border',
617
- 'type': 'line',
618
- 'source': 'geojson-source',
619
- 'layout': {
620
- 'line-join': 'round',
621
- 'line-cap': 'butt'
622
- },
623
- 'paint': {
624
- 'line-color': ['get', 'BorderColor'],
625
- 'line-width': ['get', 'BorderWidth'] // Slightly wider for border effect
626
- },
627
- 'filter': ['==', '$type', 'LineString']
628
- });
629
- map.addLayer({
630
- 'id': 'geojson-source-route',
631
- 'type': 'line',
632
- 'source': 'geojson-source',
633
- 'layout': {
634
- 'line-join': 'round',
635
- 'line-cap': 'butt'
636
- },
637
- 'paint': {
638
- 'line-color': ['get', 'Color'],
639
- 'line-width': ['get', 'LineWidth']
640
- },
641
- 'filter': ['==', '$type', 'LineString']
642
- });
643
-
644
- // Create a new FeatureCollection for the points
645
- const pointGeoJSON = {
646
- 'type': 'FeatureCollection',
647
- 'features': pointFeatures
648
563
  };
649
-
650
- // Add the GeoJSON source for the points to the map
651
- map.addSource('line-end-points', {
652
- 'type': 'geojson',
653
- 'data': pointGeoJSON
654
- });
655
-
656
- VectorUtils.getArrowHead((image) => {
657
- map.addImage('arrow-icon', image, { sdf: true });
658
- // Add a layer to display the arrows borders at the start/end points
564
+ });
565
+ }
566
+ VBI.MapRenderer._processGeoRoutes = () => {
567
+ const pointFeatures = [];
568
+ let geoJSON = VBI.VBITransformer.getTransformedJSON();
569
+ if (map.getSource('geojson-source')) {
570
+ map.getSource('geojson-source').setData(geoJSON[1]);
571
+ } else {
572
+ map.addSource('geojson-source', {
573
+ 'type': 'geojson',
574
+ 'data': geoJSON[1]
575
+ });
576
+ }
577
+ geoJSON[1].features.forEach((marker) => {
578
+ if (marker.geometry.type == "LineString") {
579
+
580
+ const routeKey = marker.properties.Key;
581
+
582
+ if (marker.properties.Label) {
583
+
584
+ if (!renderedRouteLabels.has(routeKey)) {
585
+
586
+ const midpoint = VectorUtils.getVisualMidpoint(marker.geometry.coordinates, map);
587
+
588
+ const routeLabelEl = VectorUtils.createRouteLabel(
589
+ marker.properties.Label,
590
+ marker.properties.LabelBGColor
591
+ );
592
+
593
+ const labelMarker = new maplibregl.Marker({
594
+ element: routeLabelEl,
595
+ anchor: 'left',
596
+ offset: [10, 0]
597
+ })
598
+ .setLngLat(midpoint)
599
+ .addTo(map);
600
+
601
+ renderedRouteLabels.set(routeKey, labelMarker);
602
+ }
603
+ }
604
+
605
+ const coords = marker.geometry.coordinates;
606
+ const startCoord = coords[0]; // First coordinate
607
+ const endCoord = coords[coords.length - 1]; // Last coordinate
608
+
609
+ // Calculate angle between the two points
610
+ const angle = VectorUtils.calculateBearing(startCoord, endCoord);
611
+
612
+ // Determine the arrow rotations
613
+ const normalizedStartRotation = VectorUtils.normalizeAngle(90 + angle); // Adjust for start arrow
614
+ const normalizedEndRotation = angle > 90 ? VectorUtils.normalizeAngle(270 + angle) : VectorUtils.normalizeAngle(90 - angle); // Adjust for end arrow
615
+
616
+ if (coords.length > 1) {
617
+ // Create start point
618
+ const startPoint = {
619
+ 'type': 'Feature',
620
+ 'geometry': {
621
+ 'type': 'Point',
622
+ 'coordinates': coords[0] // First coordinate
623
+ },
624
+ 'properties': {
625
+ 'Color': marker.properties.Color,
626
+ 'BorderColor': marker.properties.BorderColor,
627
+ 'arrowRotation': normalizedStartRotation,
628
+ 'size': 0.07 * parseFloat(marker.properties.LineWidth),
629
+ 'borderSize': 0.07 * (parseFloat(marker.properties.LineWidth) + 1)
630
+ }
631
+ };
632
+
633
+ // Create end point
634
+ const endPoint = {
635
+ 'type': 'Feature',
636
+ 'geometry': {
637
+ 'type': 'Point',
638
+ 'coordinates': coords[coords.length - 1] // Last coordinate
639
+ },
640
+ 'properties': {
641
+ 'Color': marker.properties.Color,
642
+ 'BorderColor': marker.properties.BorderColor,
643
+ 'arrowRotation': normalizedEndRotation,
644
+ 'size': 0.07 * parseFloat(marker.properties.LineWidth),
645
+ 'borderSize': 0.07 * (parseFloat(marker.properties.LineWidth) + 1)
646
+ }
647
+ };
648
+
649
+ // Add the points only if the arrow is supposed to be shown
650
+ if (marker.properties.StartStyle === '1') {
651
+ pointFeatures.push(startPoint);
652
+ }
653
+ if (marker.properties.EndStyle === '1') {
654
+ pointFeatures.push(endPoint);
655
+ }
656
+ }
657
+ // });
658
+ } })
659
+ // Create a new FeatureCollection for the points
660
+ const pointGeoJSON = {
661
+ 'type': 'FeatureCollection',
662
+ 'features': pointFeatures
663
+ };
664
+
665
+ // Update the GeoJSON source for the points
666
+ if (map.getSource('line-end-points')) {
667
+ map.getSource('line-end-points').setData(pointGeoJSON);
668
+ } else {
669
+ map.addSource('line-end-points', {
670
+ 'type': 'geojson',
671
+ 'data': pointGeoJSON
672
+ });
673
+ }
674
+ if (!map.getLayer('geojson-source-point')) {
675
+ map.addLayer({
676
+ 'id': 'geojson-source-point',
677
+ 'type': 'circle',
678
+ 'source': 'geojson-source',
679
+ 'paint': {
680
+ 'circle-opacity': 0 // Hide points by making them fully transparent
681
+ },
682
+ 'filter': ['==', '$type', 'Point']
683
+ });
684
+ }
685
+ if (!map.getLayer('geojson-source-route-border')) {
686
+ // First layer for the border (wider line)
687
+ map.addLayer({
688
+ 'id': 'geojson-source-route-border',
689
+ 'type': 'line',
690
+ 'source': 'geojson-source',
691
+ 'layout': {
692
+ 'line-join': 'round',
693
+ 'line-cap': 'butt'
694
+ },
695
+ 'paint': {
696
+ 'line-color': ['get', 'BorderColor'],
697
+ 'line-width': ['get', 'BorderWidth'] // Slightly wider for border effect
698
+ },
699
+ 'filter': ['==', '$type', 'LineString']
700
+ });
701
+ }
702
+ if (!map.getLayer('geojson-source-route')) {
703
+ map.addLayer({
704
+ 'id': 'geojson-source-route',
705
+ 'type': 'line',
706
+ 'source': 'geojson-source',
707
+ 'layout': {
708
+ 'line-join': 'round',
709
+ 'line-cap': 'butt'
710
+ },
711
+ 'paint': {
712
+ 'line-color': ['get', 'Color'],
713
+ 'line-width': ['get', 'LineWidth']
714
+ },
715
+ 'filter': ['==', '$type', 'LineString']
716
+ });
717
+ }
718
+
719
+ // Add a layer to display the arrows at the start/end points
720
+ if (!map.getLayer('route-end-arrows')){
659
721
  map.addLayer({
660
- 'id': 'route-end-arrows-border',
722
+ 'id': 'route-end-arrows',
661
723
  'type': 'symbol',
662
724
  'source': 'line-end-points',
663
725
  'layout': {
664
- 'icon-image': 'arrow-icon', // base64 arrow icon
665
- 'icon-size': ['get', 'borderSize'],
726
+ 'icon-image': 'arrow-icon', // Use your base64 arrow icon
727
+ 'icon-size': ['get', 'size'],
666
728
  'icon-allow-overlap': true,
667
729
  'icon-rotation-alignment': 'map',
668
730
  'icon-rotate': ['get', 'arrowRotation'], // Rotate based on calculated angle
669
731
  'icon-offset': [-12, 4]
670
732
  },
671
733
  'paint': {
672
- 'icon-color': ['get', 'BorderColor'] // Match the arrow color with the line color
734
+ 'icon-color': ['get', 'Color'] // Match the arrow color with the line color
673
735
  }
674
736
  });
737
+ }
675
738
 
676
- // Add a layer to display the arrows at the start/end points
739
+ VectorUtils.getArrowHead((image) => {
740
+ if (!map.hasImage('arrow-icon')) {
741
+ map.addImage('arrow-icon', image, { sdf: true });
742
+ }
743
+ // Add a layer to display the arrows borders at the start/end points
744
+ if (!map.getLayer('route-end-arrows-border')){
677
745
  map.addLayer({
678
- 'id': 'route-end-arrows',
746
+ 'id': 'route-end-arrows-border',
679
747
  'type': 'symbol',
680
748
  'source': 'line-end-points',
681
749
  'layout': {
682
- 'icon-image': 'arrow-icon', // Use your base64 arrow icon
683
- 'icon-size': ['get', 'size'],
750
+ 'icon-image': 'arrow-icon', // base64 arrow icon
751
+ 'icon-size': ['get', 'borderSize'],
684
752
  'icon-allow-overlap': true,
685
753
  'icon-rotation-alignment': 'map',
686
754
  'icon-rotate': ['get', 'arrowRotation'], // Rotate based on calculated angle
687
755
  'icon-offset': [-12, 4]
688
756
  },
689
757
  'paint': {
690
- 'icon-color': ['get', 'Color'] // Match the arrow color with the line color
758
+ 'icon-color': ['get', 'BorderColor'] // Match the arrow color with the line color
691
759
  }
692
- });
693
- });
694
- });
695
-
760
+ }, 'route-end-arrows');
761
+ }
762
+ });
763
+
696
764
  // Change mouse cursor when hovering over the line
697
765
  map.on('mouseenter', 'geojson-source-route', function (event) {
698
766
  if (!that.Apressed && !that.Rpressed) {
@@ -900,40 +968,8 @@ sap.ui.define([
900
968
  }
901
969
  }
902
970
  }
903
-
904
- mapCanvas.oncontextmenu = (e) => {
905
- e.preventDefault();
906
- // Get the mouse coordinates within the map container
907
- const rect = canvas.getBoundingClientRect(); // Get map container position
908
- const x = e.clientX - rect.left; // Relative to map
909
- const y = e.clientY - rect.top;
910
- const lngLat = map.unproject([x, y]);
911
- // Check if the right-click happened on the 'geojson-source-route' layer
912
- const features = map.queryRenderedFeatures([x, y], { layers: ['geojson-source-route'] });
913
-
914
- if (features.length > 0) {
915
- e.features = features;
916
- // The context menu is for the 'geojson-source-route' layer
917
- triggerPayloadRoute(e, 'CONTEXT_MENU_REQUEST');
918
- } else {
919
- // Get the mouse coordinates within the map container
920
- const offsetX = e.offsetX;
921
- const offsetY = e.offsetY;
922
- const lngLat = map.unproject([offsetX, offsetY]);
923
- // The context menu is for the map
924
- const coords = lngLat.lng + ";" + lngLat.lat + ";0.0";
925
- const currentZoom = map.getZoom();
926
- const center = map.getCenter();
927
- const currentCenter = center.lng + ";" + center.lat;
928
- const xyobj = VectorUtils.GetEventVPCoordsObj(e, map_container);
929
- const screenX = xyobj.x;
930
- const screenY = xyobj.y;
931
- PayloadGenerator.onMapContextMenu(coords, currentZoom, currentCenter, screenX, screenY);
932
- }
933
- };
934
-
935
971
  var that = this;
936
- map.on('idle', () => {
972
+ map.on('idle', () => {
937
973
  const container = map.getContainer();
938
974
  containerID = container.id;
939
975
  console.log("Map container ID:", containerID);
@@ -978,10 +1014,57 @@ sap.ui.define([
978
1014
  }
979
1015
  //Always trigger
980
1016
  PayloadGenerator.KeyboardHandler(event, VBI.MapRenderer.name);
981
- });
982
- })
1017
+ });
1018
+ }) ;
1019
+ }
1020
+ VBI.MapRenderer._processDeletion = (featureCollection) => {
1021
+
1022
+ const geoJSON = VBI.VBITransformer.getTransformedJSON();
1023
+ if (!geoJSON[1].features || !geoJSON[1].features.length) {
1024
+ geoJSON[1].features = featureCollection;
1025
+ return;
1026
+ }
1027
+ const oldFeatures = geoJSON[1].features;
1028
+
1029
+ // Collect IDs that still exist
1030
+ const remainingIds = {};
1031
+ for (let i = 0; i < featureCollection.length; i++) {
1032
+ remainingIds[featureCollection[i].properties.Key] = true;
1033
+ }
983
1034
 
1035
+ // Find deleted feature IDs
1036
+ const deletedIds = [];
1037
+ for (let i = 0; i < oldFeatures.length; i++) {
1038
+ const oldId = oldFeatures[i].properties.Key;
1039
+ if (!remainingIds[oldId]) {
1040
+ deletedIds.push(oldId);
1041
+ }
984
1042
  }
1043
+
1044
+ const deletedIds1 = new Set(deletedIds.map(id => String(id).trim()));
1045
+ // Remove all matching markers
1046
+ for (let i = VBI.MapRenderer.allMarkers.length - 1; i >= 0; i--) {
1047
+ const marker = VBI.MapRenderer.allMarkers[i];
1048
+ const key = marker.customProperties?.Key;
1049
+ if (!key) continue;
1050
+ if (deletedIds1.has(String(key).trim())) {
1051
+ marker.getElement().remove();
1052
+ VBI.MapRenderer.allMarkers.splice(i, 1);
1053
+ }
1054
+ }
1055
+
1056
+ // Update GeoJSON source
1057
+ geoJSON[1].features = featureCollection;
1058
+ map.getSource('geojson-source').setData(geoJSON [1]);
1059
+ if (map.getSource('line-end-points')) {
1060
+ map.getSource('line-end-points').setData({
1061
+ type: 'FeatureCollection',
1062
+ features: []
1063
+ });
1064
+ }
1065
+ VBI.MapRenderer._processGeoRoutes();
1066
+ }
1067
+
985
1068
  VBI.MapRenderer.actionName = (obj) => {
986
1069
  const actions = Array.isArray(obj?.SAPVB?.Actions?.Set?.Action)
987
1070
  ? obj.SAPVB.Actions.Set.Action
@@ -1033,18 +1116,18 @@ sap.ui.define([
1033
1116
  offset: [19, 15]
1034
1117
  }).setLngLat(lngLat)
1035
1118
  .setDOMContent(htmlContent)
1036
- .addTo(this.map);
1119
+ .addTo(map);
1037
1120
  }
1038
1121
 
1039
1122
  VBI.MapRenderer.closePopup = () => {
1040
1123
  this.popup?.remove();
1041
1124
  }
1042
1125
  VBI.MapRenderer.setRefMapLayerStack = (style, header) => {
1043
- this.map.setStyle(style);
1126
+ map.setStyle(style);
1044
1127
  // Add the headers only for the specific types of requests
1045
1128
  if (Object.keys(header).length != 0) {
1046
1129
  // Use transformRequest to modify requests for specific maps
1047
- this.map.transformRequest = (url, resourceType) => {
1130
+ map.transformRequest = (url, resourceType) => {
1048
1131
  return {
1049
1132
  url: url,
1050
1133
  headers: header // Add the header
@@ -1068,9 +1151,7 @@ sap.ui.define([
1068
1151
  contextMenuHandler.m_scene = xyparam[i]["#"];
1069
1152
  }
1070
1153
  }
1071
- Menu.findMenuByID(data.refID).open(true, 0, "begin top", "begin top", this.map._container, "" + contextMenuHandler.m_x + " " + contextMenuHandler.m_y + "", "fit");
1154
+ Menu.findMenuByID(data.refID).open(true, 0, "begin top", "begin top", map._container, "" + contextMenuHandler.m_x + " " + contextMenuHandler.m_y + "", "fit");
1072
1155
  }
1073
1156
 
1074
1157
  });
1075
-
1076
-