Public
Edited
Feb 6
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
async function mvrp(
data,
options = {
objective: {
minimise_num_depots: false,
travel_cost: "distance"
}
}
) {
const optimizeRequest = await axios
.post(`https://api.nextbillion.io/optimise-mvrp?` + `key=${apiKey}`, {
...data,
options
})
.then((res) => res.data);

const mvrpRequestId = optimizeRequest.id;
let attempts = 0;

while (attempts < 10) {
const result = await axios({
url: `https://api.nextbillion.io/optimise-mvrp/result?id=${mvrpRequestId}&key=${apiKey}`,
method: "GET",
headers: {}
});

if (
result.data &&
result.data.status === "Ok" &&
result.data.message == ""
) {
return result.data;
}
attempts++;
await new Promise((resolve) => setTimeout(resolve, 5 * 1000));
}
}
Insert cell
async function getData(city, nPoints, nRegions, nVehiclesPerRegion) {
const url = `https://m4aqpzp5ah6n7ilzunwyo5ma2u0ezqcc.lambda-url.us-east-2.on.aws/?region=${city}&number=${nPoints}&vehicles=${
nRegions * nVehiclesPerRegion
}&type=darp`;
const response = await fetch(url);
const json = await response.json();

const locations = [];

const nResidentialPoints = Math.floor(nPoints * RESIDENTIAL_PERCENT);
const nCommercialPoints = Math.floor(nPoints * COMMERCIAL_PERCENT);

const points = json.pointsArray.map((point) => [
parseFloat(point.pickup_longitude),
parseFloat(point.pickup_latitude)
]);

const regions = getRegions(points, nRegions);

json.pointsArray.forEach((point, pointIdx) => {
const locationType =
pointIdx < nResidentialPoints
? "residential"
: pointIdx < nResidentialPoints + nCommercialPoints
? "commercial"
: "medical";

for (const [regionId, region] of regions.entries()) {
const pt = [
parseFloat(point.pickup_longitude),
parseFloat(point.pickup_latitude)
];

if (turf.inside(turf.point(pt), region.polygon)) {
const solid = Math.random() < 0.5;
const organic = Math.random() < 0.5;
let _skills = [];
if (solid) _skills.push(skills.solid);
if (organic) _skills.push(skills.organic);
if (locationType === "medical") _skills.push(skills.medical);

locations.push({
name: locationType === "residential" ? point.name : point.business,
regionId,
point: pt,
type: locationType,
skills: _skills
});
break;
}
}
});

let vehicles = [];
let veh_idx = 0;

regions.forEach((r, r_idx) => {
// Assign one medical truck per region
vehicles.push({
name: `Truck ${veh_idx}`,
id: veh_idx,
license: json.vehicleArray.at(veh_idx++).license,
region: r_idx,
skills: [2]
});

for (let i = 0; i < inputs.nVehiclesPerRegion - 1; i++) {
// Assign the remaining trucks either [0], [1], or [0, 1]
let skills = [];
const randn = Math.floor(Math.random() * 3);
if (randn === 0) skills = [0];
else if (randn === 1) skills = [1];
else skills = [0, 1];

vehicles.push({
name: `Truck ${veh_idx}`,
id: veh_idx,
license: json.vehicleArray.at(veh_idx++)?.license,
region: r_idx,
skills
});
1;
}
});

return {
locations,
vehicles,
regions
};
}
Insert cell
function getRegions(points, count) {
const extreme = findExtremeCoordinates(points);
const bounds = [...extreme[0], ...extreme[1]];
const rest = points.filter((point) => {
return (point[0] === extreme[0][0] && point[1] === extreme[0][1]) ||
(point[0] === extreme[1][0] && point[1] === extreme[1][1])
? false
: true;
});

const sampledPoints = _.sampleSize(rest, count - 2);
sampledPoints.push(...extreme);

const delaunay = d3Delaunay.Delaunay.from(sampledPoints);
const voronoi = delaunay.voronoi(bounds);

const regions = [];
for (let point of voronoi.cellPolygons()) {
regions.push({
polygon: turf.lineToPolygon(turf.lineString(point)),
color: colors[regions.length]
});
}

return regions;
}
Insert cell
function createMap(id, center, zoom = 10) {
const nbmap = new nextbillion.maps.Map({
container: document.getElementById(id),
center: {
lat: center[1],
lng: center[0]
},
zoom,
maxZoom: 15,
minZoom: 0,
style:
"https://api.nextbillion.io/tt/style/1/style/22.2.1-9?map=2/basic_street-light&traffic_incidents=2/incidents_light",
scrollZoom: true
});

return nbmap;
}
Insert cell
Insert cell
Insert cell
Insert cell
vehiclesMap = {
const vehiclesMap = new Map();
vehicles.forEach((vehicle, idx) => {
vehiclesMap.set(idx, vehicle);
});

return vehiclesMap;
}
Insert cell
data = getData(
inputs.city.value,
inputs.nPoints + 2,
inputs.nRegions,
inputs.nVehiclesPerRegion
)
Insert cell
allLocations = data.locations
Insert cell
vehicles = data.vehicles
Insert cell
vehiclesTable = {
const vehiclesAssignedInitially = Object.fromEntries(
initialRouteOptimizationResult.flatMap((r) =>
r.result.routes.map((route) => [
route.vehicle,
route.steps.filter((step) => step.type === "pickup").length
])
)
);
return vehicles.map((vehicle) => ({
name: vehicle.name,
license: vehicle.license,
solid: vehicle.skills.includes(0) ? "Yes" : "No",
organic: vehicle.skills.includes(1) ? "Yes" : "No",
medical: vehicle.skills.includes(2) ? "Yes" : "No",
"# Jobs Assigned": vehiclesAssignedInitially[vehicle.id] ?? 0
}));
}
Insert cell
locationsTable = locations.map((l) => ({
Name: l.name,
Location: `${l.point.map((ax) => _.round(ax, 5)).join(", ")}`,
Type: _.capitalize(l.type),
Region: l.regionId,
"Solid waste": l.skills.includes(0) ? "Yes" : "No",
"Organic waste": l.skills.includes(1) ? "Yes" : "No",
"Medical waste": l.skills.includes(2) ? "Yes" : "No"
}))
Insert cell
regions = data.regions
Insert cell
recycling = allLocations[1]
Insert cell
depot = allLocations[0]
Insert cell
locations = allLocations.slice(2)
Insert cell
Insert cell
truckCapacity = 100
Insert cell
RESIDENTIAL_PERCENT = 0.7
Insert cell
COMMERCIAL_PERCENT = 0.25
Insert cell
centroid = {
const point = inputs.city.centroid.split(",");
return [point[1], point[0]];
}
Insert cell
HOSPITAL_PERCENT = 0.05
Insert cell
shiftStartTime = inputs.shiftStart.getTime() / 1000
Insert cell
shiftEndTime = dateFns
.addHours(inputs.shiftStart, inputs.shiftDuration)
.getTime() / 1000
Insert cell
skills = {
return {
solid: 0,
organic: 1,
medical: 2
};
}
Insert cell
priorities = {
return {
residential: 0,
commercial: 1,
medical: 2
};
}
Insert cell
icons = {
return {
residential: "🏠",
commercial: "🏪",
medical: "🏥",
trash: "🗑️",
depot: "🏭",
recycling: "♻️"
};
}
Insert cell
wasteVolume = {
return {
residential: 5,
commercial: 20,
medical: 30
};
}
Insert cell
turf = require("https://unpkg.com/@turf/turf@6/turf.min.js")
Insert cell
Insert cell
Insert cell
import {
guard,
colors,
renderPolygon,
d3Delaunay,
findExtremeCoordinates
} from "@texoslab/nbai-helpers"
Insert cell
Insert cell
import {
nextbillion,
secret2,
styles,
routeStyles
} from "@nbai/nextbillion-ai-dependencies"
Insert cell
Insert cell
nextbillion.setApiKey(apiKey)
Insert cell
Insert cell
dateFns = await require("https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.js")
Insert cell
html`<b>Add the stylesheet for the NB.ai SDK </b><br><code>&nbsp;&lt;link href="https://maps-gl.nextbillion.io/maps/v2/api/css" rel="stylesheet"&gt;</code>`
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