const CONVERT_TO_RADIAN_CONST = 1; // 0.0174533; function loadScript() { var script = document.createElement("script"); script.type = "text/javascript"; script.src = "https://maps.googleapis.com/maps/api/js?key=" + config["apiKey"] + "&callback=initMap&v=weekly"; script.defer = true; document.body.appendChild(script); } function initMap() { let waypointsJSON = localStorage.getItem("waypoints"); // let waypointsJSON = [{"name":"Lago Chalco 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33943361448716003,"lon":-1.73094628692019},{"name":"C. Lago Chapala 47, Anáhuac I Secc., Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33939502873152005,"lon":-1.73093370832688},{"name":"Lago Texcoco, Anáhuac I Secc, Ciudad de México, CDMX, Mexico","lat":0.33941341578307005,"lon":-1.73099749490239},{"name":"Lago Cuitzeo, Anáhuac I Secc., 11320 Ciudad de México, CDMX, Mexico","lat":0.33936825362399003,"lon":-1.73091535792726}] let returnToOrigin = localStorage.getItem("returnToOrigin"); let waypoints = JSON.parse(waypointsJSON); // console.log(">>>>>> ga/waypoints", waypointsJSON); // console.log(waypoints); const map = new google.maps.Map(document.getElementById("map"), { center: { lat: waypoints[0].lat / CONVERT_TO_RADIAN_CONST, lng: waypoints[0].lon / CONVERT_TO_RADIAN_CONST}, zoom: 8, }); class Waypoint{ constructor(name, location) { this.name = name; this.lat = location.lat(); this.lon = location.lng(); } } var poly = new google.maps.Polyline({ editable: true, path: [] }); let popSize = config["popSize"]; let numIterations = config["numIterations"]; let mutChance = config["mutChance"]; // Fisher-Yates shuffle algorithm function shuffle(individual) { let i = individual.length; while (--i > 0) { let temp = Math.floor(Math.random() * (i + 1)); [individual[temp], individual[i]] = [individual[i], individual[temp]]; } } // Generate initial population function genInitialPopulation(population) { let individual = []; //Arrego con los puntos a visitar. let zero = [0]; for(i=0; i < waypoints.length; ++i){ individual.push(i); // Agregamos el primer individuo con el orden original de los puntos. } population.push(individual) for (let i = 0; i < popSize; ++i) { // Agregamos a la poblacion el numero de individuos establecido (popSize). individual = [...Array(waypoints.length - 1).keys()].map(j => ++j); // Quitamos el 0 (punto de origen) porque es el inicio de la ruta, solo barajeamos los demas y luego lo regresamos. shuffle(individual); // Barajeamos los puntos del individuo para tener un individuo random. //console.log(">>>>> ga/individual ", i, [...Array(waypoints.length - 1).keys()].map(j => ++j), individual) population.push(zero.concat(individual)); // Regresamos el 0 (punto de origen) al inicio de la ruta. } // console.log(">>>>> ga/population ", population) } // Calculate the Haversine distance between two waypoints function getHaversineDistance(waypoint1, waypoint2) { let dlon = waypoint2.lon - waypoint1.lon; let lat1 = waypoint1.lat; let lat2 = waypoint2.lat; let dlat = lat2 - lat1; let a = Math.pow(Math.sin(dlat/2), 2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(dlon/2), 2); let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); return 3961 * c; } function calcTotalDistance(waypoints, individual) { let totalDistance = 0; for (let i = 0; i < individual.length - 1; ++i) { totalDistance += getDistanceFromLatLonInKm(waypoints[individual[i]], waypoints[individual[i+1]]); } // Add distance back to origin if returnToOrigin is set to true return returnToOrigin === "true" ? totalDistance + getHaversineDistance(waypoints[0], waypoints[individual[individual.length - 1]]) : totalDistance; } function getDistanceFromLatLonInKm(waypoint1, waypoint2) { let lat1 = waypoint1.lat; let lon1 = waypoint1.lon; let lat2 = waypoint2.lat; let lon2 = waypoint2.lon; var R = 6371; // Radius of the earth in km var dLat = deg2rad(lat2-lat1); // deg2rad below var dLon = deg2rad(lon2-lon1); var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon/2) * Math.sin(dLon/2) ; var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); var d = R * c; // Distance in km return d; } function deg2rad(deg) { return deg * (Math.PI/180) } function normalize(probabilities) { let sum = probabilities.reduce(function(a, b) { //Suma todos los valores del arreglo. return a + b; }, 0); // let x = 0; // probabilities.forEach((probability, index) => { // x += probabilities[index]; // }); // console.log("##### SUM: ",sum, x); probabilities.forEach((probability, index) => { // Cambia cada valor del arreglo con su valor dividido entre SUM. probabilities[index] /= sum; }); } function getRandomInclusive() { // Genera un numero ramdom entro 0 y 1, PERO si resulta 0, entonces regresa 1. return Math.random() == 0 ? 1 : Math.random(); } function getRandomIntInclusive(min, max) { min = Math.ceil(min); // Redondeamos hacia arriba. max = Math.floor(max); // Redondeamos hacia abajo. return Math.floor(Math.random() * (max - min + 1)) + min; // Redondeamos hacia abajo (random * (max - min + 1)). } function genNewPopulation(newPopulation, crossoverIndex, individual1, individual2) { let newIndividual = []; ++crossoverIndex; for (let i = 0; i < crossoverIndex; ++i) { newIndividual.push(individual1[i]); } for (let i = 0; i < individual2.length; ++i) { if (!newIndividual.includes(individual2[i])) { newIndividual.push(individual2[i]); } } let random = getRandomInclusive(); //console.log(random); if (random <= mutChance) { let index1 = getRandomIntInclusive(1, newIndividual.length - 1); let index2 = getRandomIntInclusive(1, newIndividual.length - 1); [newIndividual[index1], newIndividual[index2]] = [newIndividual[index2], newIndividual[index1]]; } newPopulation.push(newIndividual); } function addToPath(polyPath, latlng, count) { polyPath.push(latlng); if (count != waypoints.length+1) { new google.maps.Marker({ position: latlng, label: {text: count.toString(), color: "#00FF00"}, animation: google.maps.Animation.DROP, map: map, }); } } function startNewCalculation() { window.location.href = "index.html"; } document.getElementById("goto-index").addEventListener("click", startNewCalculation); let waypointsList = document.getElementById("waypoints-list"); /* INICIAMOS LOS CALCULOS */ let population = []; // let fitTemp = []; let sortedIndexTemp = []; genInitialPopulation(population); // Mandamos population en blanco y regresamos 128 (popSize) variaciones. for (let i = 0; i <= numIterations; ++i) { // fitness[i] <==> the ith route's total distance let fitness = []; population.forEach(individual => { fitness.push(calcTotalDistance(waypoints, individual)); // Ponemos en fitness la distancia total entre los puntos de este individuo en particular. }); // fitTemp = fitness; // console.log(">>>>> ga/fitness", fitness) let sortedIndexes = [...Array(popSize).keys()].sort((index1, index2) => { return fitness[index1] < fitness[index2] ? -1 : 1; }); // console.log(sortedIndexes); let probabilities = new Array(popSize).fill(1.0 / popSize); // No se que haga este arreglo de probabilidades, pero se usa en algoritmos geneticos. probabilities[sortedIndexes[0]] *= 6; // Al parecer tiene que ver con las posibiliddes de que un individuo se escoja para la siguiente generación. probabilities[sortedIndexes[1]] *= 6; for (let j = 0; j < popSize / 2; ++j) { probabilities[sortedIndexes[j]] *= 3; } // console.log(">>>>> ga/probabilities", probabilities); // let probs = [] = probabilities; normalize(probabilities); // console.log(calcTotalDistance(waypoints, population[sortedIndexes[0]])) // console.log(">>>>> ga/probabilities normalizadas", probabilities); if (i == numIterations) { // Si ya completamos el numero de iteraciones especificadas (numIterations), entonces salimos. let solution = population[sortedIndexes[0]]; // console.log(">>>>> POPULATION: ", population) console.log(">>>>> FITNESS: ", fitness) // console.log(">>>>> SORTED INDEXES: ", sortedIndexes) // console.log(">>>>> PROBS: ", probs); // console.log(">>>>> PROBABILITIES: ", probabilities) console.log(">>>>> GA/SOLUTION:", solution); console.log(calcTotalDistance(waypoints, solution)) let polyPath = []; let count = 0; let waypointElement = null; solution.forEach(waypointIndex => { // Generamos la polilinea para Google Maps. waypoint = waypoints[waypointIndex]; waypointElement = document.createElement("li"); waypointElement.append(waypoint.name); waypointsList.appendChild(waypointElement); addToPath(polyPath, new google.maps.LatLng(waypoint.lat / CONVERT_TO_RADIAN_CONST, waypoint.lon / CONVERT_TO_RADIAN_CONST), ++count); }); if (returnToOrigin === "true") { addToPath(polyPath, new google.maps.LatLng(waypoints[0].lat / CONVERT_TO_RADIAN_CONST, waypoints[0].lon / CONVERT_TO_RADIAN_CONST), ++count); } poly.setPath(polyPath); poly.setMap(map); break; } let index1 = 0; let index2 = 0; let random = 0; let currSum = 0; let crossoverIndex = 0; let aGoesFirst = 0; let newPopulation = []; for (let j = 0; j < popSize; ++j) { currSum = 0; random = getRandomInclusive(); for (let k = 0; k < popSize; ++k) { // Generamos index1. currSum += probabilities[k]; if (currSum >= random) { index1 = k; break; } } currSum = 0; random = getRandomInclusive(); for (let k = 0; k < popSize; ++k) { // Generamos index2. currSum += probabilities[k]; if (currSum >= random) { index2 = k; break; } } crossoverIndex = getRandomIntInclusive(1, waypoints.length - 2); aGoesFirst = getRandomIntInclusive(0, 1); //console.log("****************** ",crossoverIndex, aGoesFirst) // Si aGoesFirst = 0 o 1, entonces obtenemos nueva poblacion de una u otra forma! aGoesFirst ? genNewPopulation(newPopulation, crossoverIndex, population[index1], population[index2]) : genNewPopulation(newPopulation, crossoverIndex, population[index2], population[index1]); } population = newPopulation; } //console.log(">>>>> FITNESS TEMP: ", fitTemp) //console.log(">>>>> SORTED INDEXES TEMP: ", sortedIndexTemp) }