Public
Edited
Apr 25, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
async function nearestEntryPoint(mapDir, originMarker) {
let routeLine = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: nextbillion.utils.polyline
.decode(mutable globalRoute.geometry, 6)
.map((c) => c.reverse()),
type: "LineString"
}
}
]
};

const pointFeature = {
type: "Feature",
properties: {},
geometry: {
type: "Point",
coordinates: [originMarker.getLngLat().lng, originMarker.getLngLat().lat]
}
};
console.log("Origin :", originMarker);

const closestPoint = turf.nearestPointOnLine(routeLine, pointFeature);

// Use the closest point by haversine distance to slice the polyline points
// find point index in linestring
const index = findPointIndexInLineStringWithThreshold(
routeLine,
closestPoint,
0.001
);
console.log("ClosestPoint:", closestPoint);

//build a DM 1 x (max200) from the nearest point
const maxPoints = routeLine.features[0].geometry.coordinates.length;
const startPoint = index - 100 < 0 ? 0 : index - 100;
const nbrPoints = maxPoints < 200 ? maxPoints : 200;
const coordsOfInterest = routeLine.features[0].geometry.coordinates.slice(
startPoint,
startPoint + nbrPoints
);
// route to the closest result returned in the DM
let destString = coordsOfInterest
.map((coord) => `${coord[1]},${coord[0]}`)
.join("|");
let origString = `${originMarker.getLngLat().lat},${
originMarker.getLngLat().lng
}`;
const dmResponse = await requestDistanceMatrix(origString, destString);
let nearestIndex = -1;
let nearestValue = Number.MAX_SAFE_INTEGER;
dmResponse.rows[0].forEach((el, idx) => {
if (el[0] < nearestValue) {
nearestValue = el[0];
nearestIndex = idx;
}
});

// Now we do a directions service request and render the polyline to nearest entry point
const directionsService = new nextbillion.maps.DirectionsService();
let resp = null;

resp = await directionsService.route({
destination: {
lat: coordsOfInterest[nearestIndex][1],
lng: coordsOfInterest[nearestIndex][0]
},
origin: {
lat: originMarker.getLngLat().lat,
lng: originMarker.getLngLat().lng
}
});
const popupOffsets = {
top: [0, 0],
"top-left": [0, 0]
};

const popupIdx = (
nextbillion.utils.polyline.decode(resp.routes[0], 6).length / 2
).toFixed(0);

// pops.forEach((popup) => popup.remove());
let popup = new nextbillion.maps.Popup({
offset: 25
})
.setLngLat({
lat: nextbillion.utils.polyline.decode(resp.routes[0].geometry, 6)[
popupIdx
][0],
lng: nextbillion.utils.polyline.decode(resp.routes[0].geometry, 6)[
popupIdx
][1]
})
.setHTML(
`${(resp.routes[0].distance / 1000).toFixed(1)}km<br>${(
resp.routes[0].duration / 60
).toFixed(1)}min<br>${originMarker.code}`
)
.addTo(mapDir.map);
mutable pops.push(popup);
let geojson = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: nextbillion.utils.polyline
.decode(resp.routes[0].geometry, 6)
.map((c) => c.reverse()),
type: "LineString"
}
}
]
};

if (!mapDir.map.getSource("route-entry")) {
mapDir.map.addSource("route-entry", {
type: "geojson",
data: null
});
}
mapDir.map.getSource("route-entry").setData(geojson);
if (mapDir.map.getLayer("route-entry")) {
mapDir.map.removeLayer("route-entry");
}
mapDir.map.addLayer({
id: "route-entry",
type: "line",
source: "route-entry",
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": "#FF00EE",
"line-width": 6
}
});
}
Insert cell
function findPointIndexInLineStringWithThreshold(
lineString,
pointFeature,
threshold = 0.001
) {
const lineCoords = lineString.features[0].geometry.coordinates;
const pointCoords = pointFeature.geometry.coordinates;
// Define a helper function to compare coordinates within a threshold
const isCloseEnough = (coord1, coord2) => {
return (
Math.abs(coord1[0] - coord2[0]) <= threshold &&
Math.abs(coord1[1] - coord2[1]) <= threshold
);
};
// Find the index of the point in the line string coordinates array within the threshold
const index = lineCoords.findIndex((coord) =>
isCloseEnough(coord, pointCoords)
);
console.log("Index :", index);
// If index is less than 1, rerun the function with a larger threshold
if (index < 1) {
// Adjust the threshold
const newThreshold = 0.01;
console.log("Threshold adjusted to:", newThreshold);
const lineCoords = lineString.features[0].geometry.coordinates;
const pointCoords = pointFeature.geometry.coordinates;
// Define a helper function to compare coordinates within a threshold
const isCloseEnough = (coord1, coord2) => {
return (
Math.abs(coord1[0] - coord2[0]) <= newThreshold &&
Math.abs(coord1[1] - coord2[1]) <= newThreshold
);
};
// Find the index of the point in the line string coordinates array within the threshold
const index = lineCoords.findIndex((coord) =>
isCloseEnough(coord, pointCoords)
);
return index;
} else {
return index; // Returns the index or -1 if no close match is found
}
}
Insert cell
mutable globalRoute = undefined
Insert cell
mutable pops = [];
Insert cell
async function routeMe(mapDir) {
const directionsService = new nextbillion.maps.DirectionsService();
let wayPts = [];
let resp = null;

resp = await directionsService.route({
mode : "truck",
origin: { lat: 19.073423255244595, lng: 72.7899111533693 },
destination: { lat: 17.390379267243, lng: 78.52279820492424 }
});

// We'll need the route geometry later when determining nearest entry point
mutable globalRoute = resp.routes[0];
let geojson = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
coordinates: nextbillion.utils.polyline
.decode(resp.routes[0].geometry, 6)
.map((c) => c.reverse()),
type: "LineString"
}
}
]
};

if (!mapDir.map.getSource("route")) {
mapDir.map.addSource("route", {
type: "geojson",
data: null
});
}
mapDir.map.getSource("route").setData(geojson);
if (mapDir.map.getLayer("route")) {
mapDir.map.removeLayer("route");
}
mapDir.map.addLayer({
id: "route",
type: "line",
source: "route",
layout: {
"line-join": "round",
"line-cap": "round"
},
paint: {
"line-color": "#8D5A9E",
"line-width": 6
}
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more