Step 1:
=====
Create a Static Container Region.
Name: Saler Visiting Map
HTML Code:
<!-- Leaflet CSS & JS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<!-- MarkerCluster CSS & JS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster/dist/MarkerCluster.Default.css" />
<script src="https://unpkg.com/leaflet.markercluster/dist/leaflet.markercluster.js"></script>
<!-- Direction Arrow Plugin -->
<script src="https://unpkg.com/leaflet-polylinedecorator/dist/leaflet.polylineDecorator.js"></script>
<!-- Map container -->
<div id="map" style="height:700px; width:100%;"></div>
Static ID: map
Step 2:
=====
Create Page Items.
- P23039_SALESREP_NUMBER
- P23039_FROM_DATE
- P23039_TO_DATE
=====
Name: GET_LOCATIONS_JSON
DECLARE
l_json CLOB;
l_buffer VARCHAR2(32767);
l_amount BINARY_INTEGER := 32000;
l_pos INTEGER := 1;
l_length INTEGER;
BEGIN
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
'id' VALUE r.ID, -- Unique ID
'title' VALUE 'Time: ' || ' - ' ||
TO_CHAR(r.creation_date,'DD-MON HH24:MI'),
'lat' VALUE r.latitude,
'lng' VALUE r.longitude,
'dt' VALUE TO_CHAR(r.creation_date,'YYYY-MM-DD HH24:MI:SS') -- for start and end point marker
) RETURNING CLOB
)
INTO l_json
FROM XX_LOCATION_TRACK r
WHERE r.latitude IS NOT NULL
AND r.longitude IS NOT NULL
AND R.ACTIVE_STATUS = 1
AND r.EMPLOYEE_NUMBER = :P23039_SALESREP_NUMBER
AND ( :P23039_FROM_DATE IS NULL
OR r.creation_date >=
TO_DATE(:P23039_FROM_DATE,'YYYY-MM-DD HH24:MI:SS') )
AND ( :P23039_TO_DATE IS NULL
OR r.creation_date <=
TO_DATE(:P23039_TO_DATE,'YYYY-MM-DD HH24:MI:SS') )
ORDER BY r.creation_date asc
;
IF l_json IS NULL THEN
l_json := '[]';
END IF;
owa_util.mime_header('application/json', FALSE);
htp.p('Cache-Control: no-cache');
owa_util.http_header_close;
l_length := DBMS_LOB.getlength(l_json);
WHILE l_pos <= l_length LOOP
DBMS_LOB.read(l_json, l_amount, l_pos, l_buffer);
htp.prn(l_buffer);
l_pos := l_pos + l_amount;
END LOOP;
END;
=====
Action Name: Global function call
Action: Execute JavaScript Code
Code: initMap23004();
And Go to Function and Global Variable Declaration:
// ---------- Globals ----------
var map, markerCluster, markersMap = {};
var routeLine = null;
var startMarker = null;
var endMarker = null;
var allRouteCoords = [];
var _debounceTimer = null;
var arrowDecorator = null; // arrow mark-->
// ---------- // arrow mark-->
if(arrowDecorator){
map.removeLayer(arrowDecorator);
}
// ---------- Helpers ----------
function parseNum(v){
if(v === undefined || v === null) return null;
var n = Number(v);
return isNaN(n) ? null : n;
}
// ---------- Load FULL ROUTE ----------
function loadFullRoute(){
apex.server.process('GET_LOCATIONS_JSON',{
x01:-90,
x02:-180,
x03:90,
x04:180,
pageItems:'#P23039_SALESREP_NUMBER,#P23039_FROM_DATE,#P23039_TO_DATE'
},{
dataType:'text',
success:function(data){
var locations = JSON.parse(data || '[]');
// 🔴 SORT BY DATE (ASC)
locations.sort(function(a,b){
return new Date(a.dt) - new Date(b.dt);
});
allRouteCoords = [];
locations.forEach(function(loc){
var lat = parseNum(loc.lat);
var lng = parseNum(loc.lng);
if(lat !== null && lng !== null){
allRouteCoords.push([lat,lng]);
}
});
drawRoute();
}
});
}
// ---------- Draw Route ----------
function drawRoute(){
if(routeLine){
map.removeLayer(routeLine);
}
if(startMarker){
map.removeLayer(startMarker);
}
if(endMarker){
map.removeLayer(endMarker);
}
// pint to point Line drow
if(allRouteCoords.length > 1){
routeLine = L.polyline(allRouteCoords,{
color:'blue',
weight:3,
opacity:0.9
}).addTo(map);
// --->-->--> Direction Arrow
arrowDecorator = L.polylineDecorator(routeLine,{
patterns:[
{
offset:25,
repeat:50,
symbol:L.Symbol.arrowHead({
pixelSize:10,
polygon:false,
pathOptions:{
stroke:true,
color:'blue'
}
})
}
]
}).addTo(map);
}
// START MARKER
if(allRouteCoords.length > 0){
startMarker = L.circleMarker(allRouteCoords[0],{
radius:8,
color:'green',
fillColor:'green',
fillOpacity:1
}).addTo(map).bindPopup("Start");
}
// END MARKER
if(allRouteCoords.length > 1){
var last = allRouteCoords[allRouteCoords.length-1];
endMarker = L.circleMarker(last,{
radius:8,
color:'red',
fillColor:'red',
fillOpacity:1
}).addTo(map).bindPopup("End");
}
}
// ---------- Load Visible Markers ----------
function loadVisibleData(){
var b = map.getBounds();
var south = b.getSouth();
var west = b.getWest();
var north = b.getNorth();
var east = b.getEast();
apex.server.process('GET_LOCATIONS_JSON',{
x01:south,
x02:west,
x03:north,
x04:east,
pageItems:'#P23039_SALESREP_NUMBER,#P23039_FROM_DATE,#P23039_TO_DATE'
},{
dataType:'text',
success:function(data){
var locations = JSON.parse(data || '[]');
var newIds = {};
locations.forEach(function(loc){
if(loc && loc.id){
newIds[String(loc.id)] = true;
}
});
// Remove old markers
for(var existingId in markersMap){
if(!newIds[existingId]){
markerCluster.removeLayer(markersMap[existingId]);
delete markersMap[existingId];
}
}
// Add markers
locations.forEach(function(loc){
var id = String(loc.id);
var lat = parseNum(loc.lat);
var lng = parseNum(loc.lng);
if(lat === null || lng === null) return;
if(markersMap[id]) return;
var marker = L.marker([lat,lng]);
if(loc.title)
marker.bindPopup(loc.title);
markersMap[id] = marker;
markerCluster.addLayer(marker);
});
}
});
}
// ---------- Map Init ----------
function initMap23004(){
map = L.map('map',{preferCanvas:true})
.setView([23.8103,90.4125],7);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
maxZoom:18,
attribution:'© OpenStreetMap'
}).addTo(map);
markerCluster = L.markerClusterGroup();
map.addLayer(markerCluster);
// Load FULL ROUTE
loadFullRoute();
// Load markers
loadVisibleData();
map.on('moveend',function(){
if(_debounceTimer)
clearTimeout(_debounceTimer);
_debounceTimer = setTimeout(function(){
loadVisibleData();
},300);
});
}
// ---------- Start Map ----------
initMap23004();
