- Cambios en el Algoritmo Genetico.

- Se agregó el HASH al JSON que regresa cuando se genera el ruteo.
This commit is contained in:
2024-05-07 05:46:32 -06:00
parent 433fc32b9a
commit b44f28abc9
18 changed files with 2353 additions and 35 deletions

21
Files/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Muyang Ye
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1033
Files/OSMTools.js Normal file

File diff suppressed because it is too large Load Diff

60
Files/README.md Normal file
View File

@@ -0,0 +1,60 @@
![Header](./cover.png)
# 🌎 Travelnetics ([demo](https://muyangye.github.io/Traveling_Salesman_Solver_Google_Maps/))
The Traveling Salesman Problem statement is as follows:
```Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city?```
This problem is **NP-hard** because it is as hard as a NP-Complete problem Hamilton Cycle and doesn't have an efficient certifier (not necessarily need this to be NP-Hard though). It can not be solved within polynomial time. The reason is this:
Suppose there are 19 cities in total, then I have 19 choices of which city should I travel first, 18 choices of which
city should I travel second, ..., 1 choice of which city should I travel at last. In total, that is **19!** possibilities,
out of the **19!** possibilities, I pick the one that has the shortest total distance.
If my computer can test **one billion** tours per second. It is going to take **19!/1,000,000,000** seconds ~ **3.85 years**
to finish. Therefore, it is unfeasible to enumerate all possibilities. This project proposes a partial solution using
`Genetic Algorithm` and calls `Google Maps API` to visualize. You can also utilize this project to plan your travel over 100+ places with ease.
### You can see a demo [here](https://muyangye.github.io/Traveling_Salesman_Solver_Google_Maps/)
(please note that I am using my personal Google Maps API key to host the demo. So I've set up restrictions of daily usage limit.
If you see Google Map does not load correctly. It means the daily limit was exceeded. The settings for the demo site are
`population` of 128, `numIterations` of 10000, and `mutChance` of 0.2)
## ▶️ Steps to Run Locally
1. Replace `apiKey` attribute in `config.js` with your own Google Maps API Key. If you do not have
one, here is the [link](https://developers.google.com/maps/documentation/javascript/get-api-key)
to create one (❗❗❗ Note: Fees charged by Google may apply ❗❗❗)
2. Open `index.html`, type an address in the search bar and Google Maps' Autocomplete API will
show you a list of addresses. click on one will add a waypoint, the **first** waypoint added is the origin
3. Check `Return To Origin?` or not, which means whether the solution should include going back to the origin
3. Click `Calculate Best Route!` at the bottom of `index.html`, enjoy!
## ⚙️ Customize Yourself
### Edit `config.js`, which contains the following fields:
- `popSize`: An `integer` == Population size == The total number of individual routes
- `numIterations`: A `number` > `0` == How many iterations the Genetic Algorithm should run. Generally the
more iterations, the more GA converges
- `mutChance`: A `float` between `0` and `1` == Mutation chance, as explained in `How Does It Work?`
## 💡 How Does It Work?
### [Medium Article](https://medium.com/@realymyplus/introduction-to-genetic-algorithm-with-a-website-to-watch-it-solve-traveling-salesman-problem-live-a21105a3251a)
## ⚠Known Defects
- This project solely calculates the distance between 2 waypoints using **Haversine distance**.
However, this approach has 2 major disadvantages:
- **Shortest distance** is not always equal to **shortest time**
- **Haversine distance** calculates the distance of a straight line between 2 waypoints,
whereas there are other factors involved in the **shortest distance** such as the
**existence/straightness of a road** and/or **elevation**
- All of the above 2 problems can be solved by simply querying [Google Maps' Directions API](https://developers.google.com/maps/documentation/directions/overview),
but again, Google Maps charges very high for this API. In future versions, will add
support to let the user decide whether to use [Google Maps' Directions API](https://developers.google.com/maps/documentation/directions/overview)
or **Haversine distance** for calculating distances
- Genetic Algorithm **does not gurantee** to generate the **global optimal solution** since
Genetic Algorithm may converge fairly quickly. This is why we want `mutChance` for mutation
to add a little bit of randomness here
## 🏆Acknowledgments && Disclaimers
- This project's idea originates from `ITP 435-Professional C++` taught at the
`University of Southern California` designed by [Sanjay Madhav](https://viterbi.usc.edu/directory/faculty/Madhav/Sanjay)
- This is the first time I ever touched Javascript. I am a lifelong C++|Python|Java|PHP developer.
So please bear with me if my Javascript coding style is a mess. Any suggestions are
more than welcome!

6
Files/config.js Normal file
View File

@@ -0,0 +1,6 @@
config = {
"apiKey": "AIzaSyDYUOIGHUNDHrkdsU2B-WT7loUGdqGgCfg", // REPLACE WITH YOUR_API_KEY HERE
"popSize": 128,
"numIterations": 5000,
"mutChance": 0.4,
};

BIN
Files/cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

270
Files/genetic-algorithm.js Normal file
View File

@@ -0,0 +1,270 @@
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)
}

51
Files/index.html Normal file
View File

@@ -0,0 +1,51 @@
<html>
<head>
<title>Travelnetics</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
</head>
<body>
<div class="pac-card" id="pac-card">
<div>
<div id="title">Search for Waypoints</div>
<br />
</div>
<div id="pac-container">
<input id="pac-input" type="text" placeholder="Enter a location" />
</div>
</div>
<div id="map"></div>
<!-- Searched data -->
<ul id="waypoints-list">
</ul>
<div id="infowindow-content">
<span id="place-name" class="title"></span><br />
<span id="place-address"></span>
</div>
<div id="return-to-origin-div">
<label>Return to Origin?
<input id="return-to-origin" type="checkbox" checked>
</label>
</div>
<div id="button-div">
<button id="goto-result">Calculate Best Route!</button>
</div>
<!--
The `defer` attribute causes the callback to execute after the full HTML
document has been parsed. For non-blocking uses, avoiding race conditions,
and consistent behavior across browsers, consider loading using Promises
with https://www.npmjs.com/package/@googlemaps/js-api-loader.
-->
<script src="./config.js"></script>
<script src="./map.js"></script>
<script defer>
window.initMap = initMap;
window.onload = loadScript;
</script>
</body>
</html>

140
Files/map.js Normal file

File diff suppressed because one or more lines are too long

9
Files/osm.html Normal file
View File

@@ -0,0 +1,9 @@
<script type="text/javascript" src="OSMTools.js"></script>
<head>
<script type="text/javascript">
at.quelltextlich.osm.embedMapMarkedLocation( 47.07655, 15.43683, 11, 500, 300 );
</script>
</head>
<body>
test
</body>

24
Files/r2.html Normal file
View File

@@ -0,0 +1,24 @@
<html>
<head>
<title>Travelnetics</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
</head>
<body>
<div id="map"></div>
<h2 style="text-align: center;">Results:</h2>
<ol style="text-align: center; list-style-position: inside;" id="waypoints-list">
</ol>
<div id="button-div">
<button id="goto-index" style="cursor: pointer;">Start a New Calculation!</button>
</div>
<script src="./config.js"></script>
<script src="./genetic-algorithm.js"></script>
<script defer>
window.onload = loadScript;
window.initMap = initMap;
</script>
</body>
</html>

24
Files/result.html Normal file
View File

@@ -0,0 +1,24 @@
<html>
<head>
<title>Travelnetics</title>
<link rel="stylesheet" type="text/css" href="./style.css" />
</head>
<body>
<div id="map"></div>
<h2 style="text-align: center;">Results:</h2>
<ol style="text-align: center; list-style-position: inside;" id="waypoints-list">
</ol>
<div id="button-div">
<button id="goto-index" style="cursor: pointer;">Start a New Calculation!</button>
</div>
<script src="./config.js"></script>
<script src="./genetic-algorithm.js"></script>
<script defer>
window.onload = loadScript;
window.initMap = initMap;
</script>
</body>
</html>

95
Files/style.css Normal file
View File

@@ -0,0 +1,95 @@
/*
* Always set the map height explicitly to define the size of the div element
* that contains the map.
*/
#map {
height: 80%;
}
/*
* Optional: Makes the sample page fill the window.
*/
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#infowindow-content .title {
font-weight: bold;
}
#infowindow-content {
display: none;
}
#map #infowindow-content {
display: inline;
}
.pac-card {
background-color: #fff;
border: 0;
border-radius: 2px;
box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
margin: 10px;
padding: 0 0.5em;
font: 400 18px Roboto, Arial, sans-serif;
overflow: hidden;
font-family: Roboto;
padding: 0;
}
#pac-container {
padding-bottom: 12px;
margin-right: 12px;
}
#pac-input {
background-color: #fff;
font-family: Roboto;
font-size: 15px;
font-weight: 300;
margin-left: 12px;
padding: 0 11px 0 13px;
text-overflow: ellipsis;
width: 400px;
}
#pac-input:focus {
border-color: #4d90fe;
}
#title {
color: #fff;
background-color: #4d90fe;
font-size: 25px;
font-weight: 500;
padding: 6px 12px;
}
#button-div {
text-align: center;
}
#return-to-origin-div {
text-align: center;
font-size: large;
}
#goto-result {
border: 2px solid coral;
height: 50px;
width: 200px;
margin-bottom: 20px;
cursor: pointer;
}
#goto-index {
border: 2px solid coral;
height: 50px;
width: 200px;
margin-bottom: 20px;
cursor: pointer;
}

408
Genetic_Algorithm.bas Normal file
View File

@@ -0,0 +1,408 @@
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=10
@EndOfDesignText@
Sub Class_Globals
Dim popSize As Int = 128
Dim numIterations As Int = 100
Dim mutChance As Double = 0.4
Dim population As List
Dim bestFitness As List
Dim probabilities As List
Dim laSolucion, laSolucion2 As individuoT
Dim waypoints As List
Type individuoT (indice As Int, individuo As List, fitness As Double) ', probabilities As Double
Dim listaIndFit As List
Dim inicioCont As Double = 0
Dim final As String = ""
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
' waypoints = jsonP.
End Sub
'Resumable Subs (wait for / sleep) in server handlers
'Resumable subs can only work when there is a message queue.
'By default, server handlers end when the Handle sub is completed. They do not create a message loop.
'If you want to wait for an event then you need to call StartMessageLoop and later StopMessageLoop.
'https://www.b4x.com/android/forum/threads/resumable-subs-wait-for-sleep-in-server-handlers.81833/
Sub Handle(req As ServletRequest, resp As ServletResponse)
Log("##############################################################")
Log("############# GA/Handle ########################")
Log("##############################################################")
Private coords As String = req.GetParameter("c")
final = req.GetParameter("f")
If coords <> "" Then
Private latF As String = "0"
Private lonF As String = "0"
If final <> "" Then ' Coordenadas del punto final.
Private tmpF() As String = Regex.Split(",", final)
latF = tmpF(2)
lonF = tmpF(1)
End If
waypoints.Initialize
Private coords2() As String = Regex.Split(";", coords)
For i = 0 To coords2.Length - 1
Private tmpS() As String = Regex.Split(",", coords2(i))
If (latF <> tmpS(2) And lonF <> tmpS(1)) Or i = 0 Then ' Quitamos las coordenadas del punto final de los waypoints para agregarlas despues
waypoints.Add(CreateMap("id":tmpS(0), "lon":tmpS(1), "lat":tmpS(2)))
End If
Next
If final <> "" Then
waypoints.Add(CreateMap("id":tmpF(0), "lon":lonF, "lat":latF))
End If
End If
Log(waypoints)
resp.ContentType = "text/html"
' Dim l0 As List
' l0.Initialize2(Array As Int(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20))
' resp.Write($"${l0}<br>"$)
' Log(l0)
' For i = 0 To 10
' resp.Write($"${ShuffleList(l0)}<br>"$)
'' Log(ShuffleList(l0))
' Next
' Log($"${l0}${CRLF}${l1}"$)
population.Initialize
bestFitness.Initialize
laSolucion.Initialize
laSolucion2.Initialize
population = GenInitialPopulation(population)
inicioCont = DateTime.now
For h = 0 To numIterations
' Log("#########################################################################################")
' Log("############################## NUEVA POBLACION ###################################")
' Log("#########################################################################################")
' Log(population)
Private fitness As List
listaIndFit.Initialize
fitness.Initialize
probabilities.Initialize
For j = 0 To population.Size - 1
' Log(j)
Private thisFitness As Double = calculateTotalDistance(waypoints, population.get(j))
fitness.Add(thisFitness)
Private indFit As individuoT
indFit.indice = j
indFit.individuo = population.get(j)
indFit.fitness = thisFitness
probabilities.Add(1.0 / popSize) 'indFit.
listaIndFit.Add(indFit) 'Ponemos en listaIndFit, el Indice de la poblacion, el individuo y el fitness
Next
fitness.Sort(True)
' Log(fitness)
listaIndFit.SortType("fitness", True) ' Ordenamos la lista por fitness.
' Log("BEST FITNESS" & listaIndFit.Get(0))
bestFitness.Add(listaIndFit.Get(0)) ' Agregamos la mejor a bestFitness.
' Log("***** listaIndFit: " & listaIndFit)
' Log(listaIndFit.Size)
' Modificamos las probabilidades de selección de individuos en la población.
Private tmpIndFit As individuoT
tmpIndFit = listaIndFit.Get(0)
probabilities.Set(0, probabilities.Get(0) * 6)
listaIndFit.Set(0, tmpIndFit)
tmpIndFit = listaIndFit.Get(1)
probabilities.set(1, probabilities.Get(1) * 6)
listaIndFit.Set(1, tmpIndFit)
For i = 0 To (popSize / 2) - 1
tmpIndFit = listaIndFit.Get(i)
probabilities.set(i, probabilities.Get(i) * 3)
listaIndFit.Set(i, tmpIndFit)
Next
' Normalizamos las probabilidades de selección de individuos
Private sum As Double = 0
For i = 0 To popSize - 1
sum = sum + probabilities.Get(i)
Next
For i = 0 To popSize - 1
tmpIndFit = listaIndFit.Get(i)
probabilities.set(i, probabilities.Get(i) / sum)
listaIndFit.Set(i, tmpIndFit)
Next
If h = numIterations Then
bestFitness.SortType("fitness", True)
laSolucion = bestFitness.Get(0)
Log("#################################################################################")
Log($"################ LA SOLUCION ES: ${CRLF}${laSolucion}"$)
Log("#################################################################################")
resp.Write($"<b># LA SOLUCION ES:</b><br>${CRLF}${laSolucion}<br>"$)
resp.Write($"<b># PUNTOS:</b> ${laSolucion.individuo.Size}<br>"$)
DateTime.DateFormat="yyMMddHHmmss"
Log("TIEMPO : " & ((DateTime.Now-inicioCont)/1000))
Private coords4 As String = ""
Private laDist As Double = 0
Private lonAnt, latAnt As Double
For v = 0 To laSolucion.individuo.Size - 1
' Log( $"${laSolucion.individuo.Get(v)} - ${waypoints.Get(laSolucion.individuo.Get(v))}"$ )
Private lasCoords As Map = waypoints.Get(v)
If coords4 = "" Then ' Generamos coordenadas para ver el mapa en osm.quelltextlich.at/
coords4 = $"${lasCoords.get("lon")},${lasCoords.get("lat")}"$
Else
coords4 = $"${coords4}:${lasCoords.get("lon")},${lasCoords.get("lat")}"$
End If
If v > 0 Then
Private lasCoordsAnt As Map = waypoints.Get((v - 1))
laDist = laDist + Main.calculateDistance1(lasCoordsAnt.Get("lat"), lasCoordsAnt.Get("lon"),lasCoords.Get("lat"), lasCoords.Get("lon"))
End If
lonAnt = lasCoords.get("lon")
latAnt = lasCoords.get("lat")
Next
If final <> "" Then
' Log($"${waypoints.Get(waypoints.Size-2).As(Map).Get("id")},${waypoints.Get(waypoints.Size-2).As(Map).Get("lat")}, ${waypoints.Get(waypoints.Size-2).As(Map).Get("lon")}, ${latF}, ${lonF}"$)
' Log(laDist)
laDist = laDist + calculateDistance1(waypoints.Get(waypoints.Size-2).As(Map).Get("lat"), waypoints.Get(waypoints.Size-2).As(Map).Get("lon"), latF, lonF)
' Log(laDist)
End If
Log("DISTANCIA ORIGINAL: " & laDist)
resp.Write($"<b># DISTANCIA ORIGINAL: </b>${NumberFormat2(laDist, 1, 2, 2, True)}<br>"$)
Private coords4 As String = ""
Private laDist As Double = 0
Private lonAnt, latAnt As Double
For v = 0 To laSolucion.individuo.Size - 1
' Log( $"${laSolucion.individuo.Get(v)} - ${waypoints.Get(laSolucion.individuo.Get(v))}"$ )
Private lasCoords As Map = waypoints.Get(laSolucion.individuo.Get(v))
If coords4 = "" Then ' Generamos coordenadas para ver el mapa en osm.quelltextlich.at/
coords4 = $"${lasCoords.get("lon")},${lasCoords.get("lat")}"$
Else
coords4 = $"${coords4}:${lasCoords.get("lon")},${lasCoords.get("lat")}"$
End If
If v > 0 Then
Private lasCoordsAnt As Map = waypoints.Get(laSolucion.individuo.Get(v - 1))
' Log($"${laSolucion.individuo.Get(v - 1)} -> ${laSolucion.individuo.Get(v)} = ${numberformat2(laDist, 1, 2, 2, True)} + ${Main.calculateDistance1(lasCoordsAnt.Get("lat"), lasCoordsAnt.Get("lon"),lasCoords.Get("lat"), lasCoords.Get("lon"))} = ${numberformat2(ladist + Main.calculateDistance1(lasCoordsAnt.Get("lat"), lasCoordsAnt.Get("lon"),lasCoords.Get("lat"), lasCoords.Get("lon")),1,2,2,True)}"$)
laDist = laDist + Main.calculateDistance1(lasCoordsAnt.Get("lat"), lasCoordsAnt.Get("lon"),lasCoords.Get("lat"), lasCoords.Get("lon"))
End If
lonAnt = lasCoords.get("lon")
latAnt = lasCoords.get("lat")
Next
Log("DISTANCIA MEJORADA: " & laDist)
resp.Write($"<b># DISTANCIA MEJORADA: </b>${NumberFormat2(laDist, 1, 2, 2, True)}"$)
' Private laDistOrig As Double = 0
' Private origCoords As String = "-98.367397,20.2885251:-98.3673986,20.2842333:-98.4171617,20.2875307:-98.4171617,20.2870722:-98.2018217,20.157312:-98.2037883,20.1579131:-98.2033956,20.1597869:-98.2037471,20.1600194:-98.3304542,20.2411999:-98.3675814,20.157501:-98.3981804,20.1613062:-98.3981877,20.1611756:-98.3981638,20.1611726:-98.3981838,20.1528387:-98.4105988,20.1604658:-98.4105797,20.1604658:-98.4106149,20.1601633:-98.4105995,20.1582812:-98.4171635,20.0934635:-98.3746171,20.0937065:-98.3113997,20.0373747:-98.3097373,20.0378:-98.3097854,20.0378621:-98.3097373,20.03822:-98.3091305,20.0382337:-98.3092364,20.0295646:-98.3020753,20.0281433:-98.2993573,19.9890079:-98.2994654,20.0191821:-98.2997214,20.0191883:-98.2998736,20.0373747:-98.2965265,20.0374267:-98.2974103,20.0382089:-98.2974578,20.0382999:-98.2998506,20.0373507:-98.3001282,20.0374151:-98.3011024,20.0382249:-98.3019379,20.0373344:-98.3024263,20.0382022:-98.3024866,20.0381959:-98.3047418,20.0372789:-98.3048551,20.0373969:-98.3087633,20.0373588:-98.3091585,20.038213:-98.3096702,20.0382294:-98.3098262,20.0382301:-98.3098019,20.0382226:-98.3098507,20.0382032:-98.309962,20.0382169:-98.310657,20.0382241:-98.3545391,20.0765822:-98.3674448,20.0883075:-98.3674109,20.0883744:-98.3717499,20.0904887:-98.3733305,20.0934303:-98.3729919,20.1005734:-98.3674397,20.1069279:-98.3597017,20.1002531:-98.3603581,20.0989735:-98.3578046,20.0983058:-98.3575839,20.0997116:-98.3571027,20.102067:-98.3575733,20.102067:-98.35414,20.106525:-98.3547467,20.1085665:-98.3569592,20.1102607:-98.3543659,20.097339:-98.354654,20.0976982:-98.355117,20.1066587:-98.3568694,20.1071554:-98.3571229,20.106387:-98.3602375,20.1011948:-98.359305,20.1076649:-98.3595572,20.1072504:-98.3600443,20.1044867:-98.360066,20.1044785:-98.367437,20.0989306:-98.3683624,20.097075:-98.3717737,20.0960621:-98.3732723,20.0958825:-98.3739688,20.0950052:-98.3744864,20.0959816:-98.3748629,20.0952055:-98.4171079,20.0942:-98.4171454,20.0950241:-98.4171373,20.0951271:-98.4171682,20.0969286:-98.4782102,20.0689478:-98.4821405,20.0794694:-98.5321791,20.0950872:-98.5316393,20.0950869:-98.5316151,20.0950928:-98.5313826,20.0950772:-98.5311545,20.0950977:-98.5311341,20.0950882:-98.5307409,20.0950772:-98.5310337,20.0880191:-98.5117017,20.0950945:-98.4964301,20.095097:-98.4954019,20.0950948:-98.4797164,20.0950921:-98.4793191,20.0950853:-98.4773212,20.0950898:-98.4731448,20.0950862:-98.4171953,20.0969398:-98.4171583,20.1079494:-98.4158139,20.1541421:-98.4185455,20.1598054:-98.4175739,20.1604813:-98.4171777,20.1600988:-98.4171707,20.1586434:-98.410603,20.1551811:-98.4018328,20.1582555:-98.4017017,20.1586705:-98.4015936,20.1598066:-98.3986541,20.1600943:-98.3981603,20.1599041:-98.3675814,20.1600466:-98.3674102,20.160304:-98.3674328,20.2380154:-98.3674147,20.2631807:-98.3580592,20.2664201:-98.348528,20.2820715:-98.3472617,20.2878516:-98.3470749,20.2869986:-98.3473113,20.285622:-98.3485247,20.2856734:-98.3485252,20.2840998:-98.3673448,20.2845417:-98.3674329,20.2875573:-98.3674382,20.287592:-98.3674382,20.2879142"
'' Private origCoords As String = "ESE05002,-98.367397,20.2885251;ESE05003,-98.3674382,20.287592;ESE05004,-98.3674382,20.2879142;ESE05005,-98.3304542,20.2411999;ESE05006,-98.3674147,20.2631807;ESE05007,-98.348528,20.2820715;ESE05008,-98.3981838,20.1528387;ESE05009,-98.4158139,20.1541421;ESE05010,-98.410603,20.1551811;ESE05011,-98.4105995,20.1582812;ESE05012,-98.3674102,20.160304;ESE05013,-98.2018217,20.157312;ESE05014,-98.3986541,20.1600943;ESE05015,-98.3717737,20.0960621;ESE05016,-98.3733305,20.0934303;ESE05017,-98.354654,20.0976982;ESE05018,-98.3543659,20.097339;ESE05019,-98.3578046,20.0983058;ESE05020,-98.3597017,20.1002531;ESE05021,-98.359305,20.1076649;ESE05022,-98.367437,20.0989306;TSE0555000,-98.3683624,20.097075;115683,-98.3744864,20.0959816;115685,-98.3746171,20.0937065;115686,-98.3717499,20.0904887;115687,-98.4171454,20.0950241;115688,-98.4171373,20.0951271;115690,-98.4171953,20.0969398;115693,-98.3729919,20.1005734;115694,-98.3732723,20.0958825;115695,-98.4171635,20.0934635;115696,-98.4171583,20.1079494;115697,-98.35414,20.106525;115699,-98.355117,20.1066587;115701,-98.3575839,20.0997116;115702,-98.3575733,20.102067;115703,-98.3603581,20.0989735;115707,-98.3600443,20.1044867;115712,-98.3571027,20.102067;115713,-98.3602375,20.1011948;115714,-98.360066,20.1044785;115715,-98.3674397,20.1069279;115717,-98.3595572,20.1072504;115718,-98.3568694,20.1071554;115720,-98.3470749,20.2869986;115721,-98.3580592,20.2664201;115722,-98.3674329,20.2875573;115723,-98.4171617,20.2870722;115725,-98.3473113,20.285622;115726,-98.3485247,20.2856734;115728,-98.4171617,20.2875307;115734,-98.3673448,20.2845417;115740,-98.5307409,20.0950772;115741,-98.5313826,20.0950772;115742,-98.5311545,20.0950977;115743,-98.3092364,20.0295646;115744,-98.2993573,19.9890079;115745,-98.3001282,20.0374151;115746,-98.2998736,20.0373747;115747,-98.3113997,20.0373747;115748,-98.3048551,20.0373969;115749,-98.3981877,20.1611756;115750,-98.3981638,20.1611726;115751,-98.2033956,20.1597869;115752,-98.3981603,20.1599041;115753,-98.3675814,20.1600466;115754,-98.3981804,20.1613062;115755,-98.3675814,20.157501;115756,-98.2037883,20.1579131;115757,-98.4018328,20.1582555;115760,-98.4017017,20.1586705;115761,-98.2037471,20.1600194;115764,-98.5316151,20.0950928;115765,-98.4731448,20.0950862;115767,-98.4793191,20.0950853;115768,-98.4185455,20.1598054;115769,-98.4773212,20.0950898;115770,-98.4782102,20.0689478;115771,-98.5117017,20.0950945;115772,-98.5321791,20.0950872;115773,-98.5310337,20.0880191;115774,-98.4797164,20.0950921;115775,-98.4954019,20.0950948;115776,-98.4964301,20.095097;115778,-98.2974103,20.0382089;115779,-98.2974578,20.0382999;115780,-98.2997214,20.0191883;115781,-98.2965265,20.0374267;115782,-98.3096702,20.0382294;115783,-98.3020753,20.0281433;115784,-98.3097373,20.0378;115786,-98.3097373,20.03822;115787,-98.3024263,20.0382022;115788,-98.3024866,20.0381959;115789,-98.2998506,20.0373507;115792,-98.3098507,20.0382032;115793,-98.3019379,20.0373344;115794,-98.3098262,20.0382301;115795,-98.2994654,20.0191821;115797,-98.4171682,20.0969286;115799,-98.3748629,20.0952055;115803,-98.3739688,20.0950052;115843,-98.3087633,20.0373588;115844,-98.3098019,20.0382226;115845,-98.309962,20.0382169;115847,-98.3097854,20.0378621;115849,-98.3547467,20.1085665;115850,-98.3571229,20.106387;115851,-98.3569592,20.1102607;115852,-98.3011024,20.0382249;115853,-98.3545391,20.0765822;115854,-98.310657,20.0382241;115860,-98.4175739,20.1604813;115864,-98.5311341,20.0950882;357004,-98.3472617,20.2878516;357005,-98.3485252,20.2840998;357006,-98.3674328,20.2380154;357007,-98.3673986,20.2842333;357089,-98.4105797,20.1604658;357090,-98.4106149,20.1601633;357091,-98.4105988,20.1604658;357092,-98.4015936,20.1598066;357093,-98.4171777,20.1600988;357094,-98.4171707,20.1586434;357195,-98.5316393,20.0950869;357196,-98.4821405,20.0794694;357305,-98.3091585,20.038213;357306,-98.3091305,20.0382337;357307,-98.3674109,20.0883744;357308,-98.3674448,20.0883075;357309,-98.3047418,20.0372789;357651,-98.4171079,20.0942"
'
' Private oc() As String = Regex.Split(":", origCoords)
' For w = 0 To oc.Length - 1
' Private lascoords2() As String = Regex.Split(",", oc(w))
' Private lat2 As Double = lascoords2(1)
' Private lon2 As Double = lascoords2(0)
' If w > 0 Then
' Private lascoordsAnt2() As String = Regex.Split(",", oc(w - 1))
' Private latAnt2 As Double = lascoordsAnt2(1)
' Private lonAnt2 As Double = lascoordsAnt2(0)
' laDistOrig = laDistOrig + Main.calculateDistance1(latAnt2, lonAnt2, lat2, lon2)
' End If
' Next
' Log("laDistOrig: " & laDistOrig)
Log("Liga para ver la ruta en mapa:" & CRLF)
Log($"https://osm.quelltextlich.at/viewer-js.html?kml_url=http://keymon.lat:9001/kmz.php?c=${coords4}"$)
resp.Write($"<br><b># FITNESS :</b> ${CRLF}${NumberFormat2(laSolucion.fitness, 1, 2, 2, True)}"$)
resp.Write($"<br><b># TIEMPO :</b> ${CRLF}${(DateTime.Now-inicioCont)/1000}"$)
End If
Private index1 As Int = 0
Private index2 As Int = 0
Private random As Double = 0
Private currSum As Double = 0
Private crossoverIndex As Int = 0
Private aGoesFirst As Int = 0
Private newPopulation As List
newPopulation.Initialize
' newPopulation.Add(listaIndFit.Get(0))
' Log(listaIndFit.Size)
For k = 0 To popSize - 1
currSum = 0
random = getRandomInclusive
For m = 0 To popSize - 1
currSum = currSum + probabilities.Get(m)
' Log(listaIndFit.Get(m).As(individuoT).probabilities)
' Log($">>>>>> currSum: ${currSum}, random: ${random}"$)
If currSum >= random Then
index1 = m
Exit
End If
Next
currSum = 0
random = getRandomInclusive
For m = 0 To popSize - 1
currSum = currSum + probabilities.Get(m)
currSum = currSum + probabilities.Get(listaIndFit.Get(m).As(individuoT).indice)
' Log(listaIndFit.Get(m).As(individuoT).probabilities)
' Log($">>>>>> currSum: ${currSum}, random: ${random}"$)
If currSum >= random Then
index2 = m
Exit
End If
Next
crossoverIndex = getRandomIntInclusive(1, waypoints.Size - 2)
aGoesFirst = getRandomIntInclusive(0, 1)
' Log($"Index1: ${index1}, Index2: ${index2}, CrossoverIndex: ${crossoverIndex}, aGoesFirst: ${aGoesFirst}, WPS: ${waypoints.Size - 2}"$)
If aGoesFirst = 0 Then
newPopulation = genNewPopulation(newPopulation, crossoverIndex, population.Get(index1), population.Get(index2))
Else
newPopulation = genNewPopulation(newPopulation, crossoverIndex, population.Get(index2), population.Get(index1))
End If
If k = 0 Then
newPopulation.Add(listaIndFit.Get(0).As(individuoT).individuo)
' Log("+++++ " & listaIndFit.Get(0))
End If
Next
population = newPopulation
' Log("POPULATION: " & CRLF & population.Get(0))
' Log(listaIndFit)
' Log(listaIndFit.Size)
Next
End Sub
Sub GenInitialPopulation(population0 As List) As List
Dim individual As List
Dim primero, tempInd As List
Private pFinal As Int
Dim shuffled As List
Private nuevoIndividuo As List
individual.Initialize
primero.Initialize
tempInd.Initialize
shuffled.Initialize
For i = 0 To waypoints.size - 1 ' Generamos un individuo con el orden original de la ruta (0,1,2,3,4,5... etc)
individual.Add(i)
Next
primero.AddAll(individual)
tempInd.AddAll(individual)
population0.Add(primero) 'Agregamos el individuo con el orden original al principio de la poblacion.
If final <> "" Then pFinal = tempInd.get(tempInd.Size - 1)
Log("## " & tempInd)
For i = 0 To popSize - 1
If final <> "" Then tempInd.RemoveAt(tempInd.Size - 1) ' Quitamos el punto final, porque es el punto final de la ruta.
tempInd.RemoveAt(0) ' Quitamos el punto inicial, porque es el punto de inicio
' Log(tempInd)
' nuevoIndividuo = individual
shuffled = ShuffleList(tempInd)
' Log(shuffled)
shuffled.InsertAt(0, 0)
If final <> "" Then shuffled.Add(pFinal) ' Agregamos el punto final al final del individuo.
' Log(shuffled)
nuevoIndividuo.Initialize
nuevoIndividuo.AddAll(shuffled)
' Log(nuevoIndividuo)
' Log("#")
population0.Add(nuevoIndividuo)
Next
' Log("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
' Log($"POPULATION0: ${population0}"$)
Return population0
End Sub
Sub ShuffleList(pl As List) As List
' Private l As List
' l = pl
For i = pl.Size - 1 To 0 Step -1
Dim j As Int
Dim k As Object
j = Rnd(0, i + 1)
k = pl.Get(j)
pl.Set(j,pl.Get(i))
pl.Set(i,k)
Next
Return pl
End Sub
'Calculate distance - Haversine
'Using average radius of earth 6378.137 km.
Sub calculateDistance1(Latitude1 As Double, Longitude1 As Double, Latitude2 As Double, Longitude2 As Double) As Double 'ignore
Return NumberFormat2(2 * 6378137 * ASin (Sqrt (SinD ((Latitude2 - Latitude1) * 0.5) * SinD ((Latitude2 - Latitude1) * 0.5) + CosD (Latitude1) * CosD (Latitude2) * SinD ((Longitude2 - Longitude1) * 0.5) * SinD ((Longitude2 - Longitude1) * 0.5))), 1, 2, 2, False)
End Sub
Sub calculateTotalDistance(waypoints0 As List, individual As List) As String
Dim totalDistance As Double = 0
' Log("*******************************************************************************")
' Log(individual)
For i = 0 To individual.Size - 2
Private nuevaDist As Double = calculateDistance1(waypoints0.Get(individual.get(i)).As(Map).get("lat"), waypoints0.Get(individual.get(i)).As(Map).get("lon"), waypoints0.Get(individual.get(i+1)).As(Map).get("lat"), waypoints0.Get(individual.get(i+1)).As(Map).get("lon"))
' Log(">>>>>>> " & i & "|" & totalDistance & " + " & nuevaDist)
totalDistance = totalDistance + nuevaDist
' Log($"${waypoints0.Get(individual.get(i))}, ${waypoints0.Get(individual.get(i)).As(Map).get("lon")}, ${waypoints0.Get(individual.get(i)).As(Map).get("lat")}"$)
' Log($"${waypoints0.Get(individual.get(i+1))}, ${waypoints0.Get(individual.get(i+1)).As(Map).get("lon")}, ${waypoints0.Get(individual.get(i+1)).As(Map).get("lat")}"$)
' Log( totalDistance)
Next
' Log(totalDistance)
Return totalDistance
End Sub
Sub getRandomInclusive As Double
Private j As Double
j = Rnd(0, 10000000000)
j = j / 10000000000
' Log(j)
If j = 0 Then j = 1
' Log(j)
Return j
End Sub
Sub getRandomIntInclusive(Min0 As Int, Max0 As Int) As Int
Min0 = Ceil(Min0) ' Redondeamos hacia arriba.
Max0 = Floor(Max0) ' Redondeamos hacia abajo.
Private j As Double
j = Rnd(Min0, Max0 + 1)
Return j
End Sub
Sub genNewPopulation(newPopulation As List, crossoverIndex As Int, individual1 As List, individual2 As List) As List
Private newIndividual As List
Private index1, index2 As Int
Private random As Double
Private lTemp1, lTemp2 As Int
newIndividual.Initialize
crossoverIndex = crossoverIndex + 1
' Log(crossoverIndex + 1)
For i = 0 To crossoverIndex - 1
newIndividual.Add(individual1.get(i))
Next
For i = 0 To individual2.Size - 1
If newIndividual.IndexOf(individual2.get(i)) = -1 Then
newIndividual.Add(individual2.get(i))
End If
Next
' Log(newIndividual)
random = getRandomInclusive
' Log(random & "|" & mutChance)
' Log(newIndividual.size)
If random <= mutChance Then
index1 = getRandomIntInclusive(1, newIndividual.Size - 1)
index2 = getRandomIntInclusive(1, newIndividual.Size - 1)
' Log($"${index1}, ${index2}, ${newIndividual.Size}"$)
' Log(newIndividual)
lTemp1 = newIndividual.Get(index1)
lTemp2 = newIndividual.Get(index2)
newIndividual.Set(index1, lTemp2)
newIndividual.Set(index2, lTemp1)
End If
newIndividual.RemoveAt(newIndividual.IndexOf(newIndividual.Size - 1))
newIndividual.Add(newIndividual.Size)
newPopulation.Add(newIndividual)
' Log(newIndividual)
Return newPopulation
End Sub
Sub traeDistanciaDesdeMatriz(punto1 As Int, punto2 As Int)
Private id1 As String = waypoints.Get(punto1).As(Map).Get("id")
Private id2 As String = waypoints.Get(punto2).As(Map).Get("id")
Private c As ResultSet = Main.db.ExecQuery($"select ${id1} from R113A68_7A7FC9F7_matriz where idT = '${id2}'"$)
End Sub

62
Genetic_Algorythm.bas Normal file
View File

@@ -0,0 +1,62 @@
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=10
@EndOfDesignText@
Sub Class_Globals
Dim population As List
Dim sortedIndexTemp As List
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
End Sub
'Resumable Subs (wait for / sleep) in server handlers
'Resumable subs can only work when there is a message queue.
'By default, server handlers end when the Handle sub is completed. They do not create a message loop.
'If you want to wait for an event then you need to call StartMessageLoop and later StopMessageLoop.
'https://www.b4x.com/android/forum/threads/resumable-subs-wait-for-sleep-in-server-handlers.81833/
Sub Handle(req As ServletRequest, resp As ServletResponse)
Log("##############################################################")
Log("############# GA/Handle ########################")
Log("##############################################################")
resp.ContentType = "text/html"
Dim l0 As List
l0.Initialize2(Array As Int(1,2,3,4,5))
resp.Write($"${Shuffle(l0)}"$)
End Sub
Sub genInitialPopulation(population1 As List) ' Mandamos population en blanco y regresamos 128 (popSize) variaciones.
End Sub
'Generate random string array
Sub ShuffleArray(StringArray() As String)
Dim ArrayVal As String
Dim Random As Int
For i = 0 To StringArray.Length - 1
Random = Rnd(i, StringArray.Length)
ArrayVal = StringArray(i)
StringArray(i) = StringArray(Random)
StringArray(Random) = ArrayVal
Next
End Sub
'Generate random string array
Sub Shuffle(l As List) As List
Dim tmpVal As String
Dim Random As Int
Private l1 As List
l1.Initialize
For i = 0 To l.Size - 1
Random = Rnd(i, l.Size)
tmpVal = l.get(i)
l1.InsertAt(i, l.get(Random))
l1.InsertAt(Random, tmpVal)
Next
Return l1
End Sub

View File

@@ -1,24 +1,45 @@
AppType=StandardJava
Build1=Default,b4j.example
File1=config.js
File2=cover.png
File3=genetic-algorithm.js
File4=index.html
File5=LICENSE
File6=map.js
File7=README.md
File8=result.html
File9=style.css
FileGroup1=Default Group
FileGroup2=Default Group
FileGroup3=Default Group
FileGroup4=Default Group
FileGroup5=Default Group
FileGroup6=Default Group
FileGroup7=Default Group
FileGroup8=Default Group
FileGroup9=Default Group
Group=Default Group
Library1=compressstrings
Library10=byteconverter
Library2=jcore
Library3=jfx
Library4=jokhttputils2
Library5=jrandomaccessfile
Library6=jserver
Library7=json
Library8=jsql
Library9=nhcalculatehash
Module1=DBRequestManager
Module2=delDB
Module3=Mapa
Module4=rutaCompleta
Module5=Ruteador
NumberOfFiles=0
NumberOfLibraries=10
NumberOfModules=5
Library1=byteconverter
Library10=nhcalculatehash
Library11=javaobject
Library2=compressstrings
Library3=jcore
Library4=jfx
Library5=jokhttputils2
Library6=jrandomaccessfile
Library7=jserver
Library8=json
Library9=jsql
Module1=Ayuda
Module2=DBRequestManager
Module3=delDB
Module4=Genetic_Algorithm
Module5=Mapa
Module6=rutaCompleta
Module7=Ruteador
NumberOfFiles=9
NumberOfLibraries=11
NumberOfModules=7
Version=10
@EndOfDesignText@
#Region Project Attributes
@@ -38,7 +59,7 @@ Version=10
#End Region
Sub Process_Globals
Private srvr As Server
Dim srvr As Server
Dim db As SQL
' Dim fx As JFX
Dim punteoLista As List
@@ -52,6 +73,7 @@ Sub Process_Globals
' Dim estePunto() As String
Dim error As String = ""
Dim msg As String = ""
Dim hash As String = ""
End Sub
Sub AppStart (Args() As String)
@@ -72,9 +94,7 @@ Sub AppStart (Args() As String)
End If
Next
End If
' Log($"ARGS=${DBRIp}:${DBRPort}"$)
Log("Server Port=" & srvr.Port)
ts.Initialize
@@ -85,12 +105,20 @@ Sub AppStart (Args() As String)
srvr.AddHandler("/mapa", "Mapa", False)
srvr.AddHandler("/rutacompleta", "rutaCompleta", False)
srvr.AddHandler("/borrar", "delDB", False)
srvr.AddHandler("/ayuda", "Ayuda", False)
srvr.AddHandler("/help", "Ayuda", False)
srvr.AddHandler("/h", "Ayuda", False)
srvr.AddHandler("/ga", "Genetic_Algorithm", False)
Log(File.ListFiles(File.DirApp))
Dim jo As JavaObject = srvr
jo.GetFieldJO("context").RunMethodJO("getMimeTypes", Null).RunMethod("addMimeMapping", Array("kml", "text/xml"))
srvr.Start
StartMessageLoop
'open browser and navigate to: http://127.0.0.1:51042/
End Sub
#Region Shared Files
@@ -153,7 +181,7 @@ Sub creaTablas(params As Map)
Log(params)
Private almacen As String = params.Get("almacen")
Private estasCoords As String = params.Get("coords")
Private hash As String = params.Get("hash")
hash = params.Get("hash")
Private ruta As String = $"${params.Get("ruta")}A${almacen}_${hash}"$
db.BeginTransaction
Try
@@ -281,6 +309,10 @@ Sub generaMatrizOSRM(ruta As String) As ResumableSub 'ignore
If j.Success Then
' Log(j.GetString)
Private j0 As String = j.GetString
Else
Log($"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ${CRLF} ${j.ErrorMessage}"$)
If error = "" Then error = j.ErrorMessage
Log($"######################### ${error}"$)
End If
j.Release
' StopMessageLoop
@@ -300,6 +332,7 @@ Sub generaMatrizOSRM(ruta As String) As ResumableSub 'ignore
' fx.ShowExternalDocument($"http://router.project-osrm.org/table/v1/driving/${lasCoords}"$)
Log("Matriz OSRM generada")
Catch
Log(456)
Log(LastException)
If error = "" Then error = LastException
End Try
@@ -394,7 +427,7 @@ Private Sub b_tiempos_Click
' tiempos("R1")
End Sub
'Regresa El tiempo y distancia de la ruta especificada.
'Regresa El tiempo y distancia de la ruta especificada desde el API de OSRM.
Sub tiempos(ruta As String) As ResumableSub 'ignore
Log("#######################################################")
Log("############# Main/tiempos ####################")
@@ -422,6 +455,10 @@ Sub tiempos(ruta As String) As ResumableSub 'ignore
' Log("RESPONSE:")
' Log(j.GetString)
Private j0 As String = j.GetString
Else
Log($"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ${CRLF} ${j.ErrorMessage}"$)
If error = "" Then error = j.ErrorMessage
Log($"######################### ${error}"$)
End If
j.Release
' StopMessageLoop
@@ -434,7 +471,7 @@ Sub tiempos(ruta As String) As ResumableSub 'ignore
'' Log(colroot)
'' Next
' Log("*****************************************")
ts.Put(ruta, CreateMap("code":"KO", "duration":0, "distance":0, "puntos":0))
ts.Put(ruta, CreateMap("code":"KO", "duration":0, "distance":0, "puntos":0, "mensaje":error))
Try
Private m As Map = js.NextObject
' Log(m)
@@ -446,11 +483,12 @@ Sub tiempos(ruta As String) As ResumableSub 'ignore
' Log("Distance: " & rutas.Get("distance"))
' Log("Legs: " & rutas.Get("legs").As(List).Size)
' Log("Waypoints: " & waypoints.Size)
ts.Put(ruta, CreateMap("code":"OK", "duration":rutas.Get("duration"), "distance":rutas.Get("distance"), "puntos":rutas.Get("legs").As(List).Size))
ts.Put(ruta, CreateMap("code":"OK", "hash":hash, "duration":rutas.Get("duration"), "distance":rutas.Get("distance"), "puntos":rutas.Get("legs").As(List).Size))
' Log(">>>>>>>>>>>>>>>>>>>>>>>>>>>" & ts)
Catch
Log(LastException)
If error = "" Then error = LastException
Log($"######################### ${error}"$)
End Try
End If
Return 1
@@ -635,7 +673,7 @@ Public Sub calculateDistance3(lat1 As Double, lon1 As Double, lat2 As Double, lo
Return Round(Yards)
Catch
Log("CalcDistance " & LastException)
if error = "" then error = LastException
If error = "" Then error = LastException
Return -1
End Try
End Sub

View File

@@ -4,18 +4,24 @@ ModuleBookmarks2=
ModuleBookmarks3=
ModuleBookmarks4=
ModuleBookmarks5=
ModuleBookmarks6=
ModuleBookmarks7=
ModuleBreakpoints0=
ModuleBreakpoints1=
ModuleBreakpoints2=
ModuleBreakpoints3=
ModuleBreakpoints4=
ModuleBreakpoints5=
ModuleBreakpoints6=
ModuleBreakpoints7=
ModuleClosedNodes0=
ModuleClosedNodes1=
ModuleClosedNodes2=
ModuleClosedNodes3=
ModuleClosedNodes4=
ModuleClosedNodes5=3
NavigationStack=rutaCompleta,generaMatrizRuteoTiempos,85,0,rutaCompleta,tiempos,105,0,Main,calculateDistance3,607,0,Ruteador,tiempos,166,0,Main,ruteo,308,0,Ruteador,Handle,38,0,Main,creaTablas,162,6,Main,generaMatrizLocal,217,0,Ruteador,generaMatrizRuteoTiempos,67,6,Main,ruteoNearestInsertion,479,4
ModuleClosedNodes4=5,6,8,9,10
ModuleClosedNodes5=
ModuleClosedNodes6=
ModuleClosedNodes7=6,7
NavigationStack=Ruteador,Class_Globals,9,4,Ruteador,Handle,28,0,Main,ruteoNearestNeighbor,466,0,Genetic_Algorithm,traeDistanciaDesdeMatriz,398,6,Main,Process_Globals,31,6,Main,creaTablas,143,0,Mapa,ruteoCompleto,167,4,Ruteador,generaMatrizRuteoTiempos,125,0,Ruteador,tiempos,194,0,Main,tiempos,441,4
SelectedBuild=0
VisibleModules=5,3,4,1,2
VisibleModules=7,5,6,2,3,4,1

View File

@@ -13,6 +13,8 @@ Sub Class_Globals
Dim m, m2 As Map
Dim getHash As CalculateHash
Dim js As JSONGenerator
Dim cuantosPuntos As Int = 0
Dim elHash As String
End Sub
Public Sub Initialize
@@ -30,18 +32,31 @@ Sub Handle(req As ServletRequest, resp As ServletResponse)
Log("##############################################################")
' Log("q='"&req.GetParameter("q")&"'")
' Log($"REQ: ${req.FullRequestURI}"$)
Private elHash As String = getHash.CalculateTheHash(req.FullRequestURI)
elHash = getHash.CalculateTheHash(req.FullRequestURI)
' Log(elHash)
Private ruta As String = req.GetParameter("r")
Private almacen As String = req.GetParameter("a")
Private coords As String = req.GetParameter("c")
Private matriz As String = req.GetParameter("m")
Private ayuda As String
If req.GetParameter("h") <> "" Then ayuda = req.GetParameter("h")
If req.GetParameter("h") <> "" Then ayuda = req.GetParameter("help")
If req.GetParameter("h") <> "" Then ayuda = req.GetParameter("ayuda")
Main.inicio = req.GetParameter("i")
Main.final = req.GetParameter("f")
If matriz <> "" And matriz <> "OSRM" Then matriz = ""
' Log($"r: ${ruta}, a: ${almacen}, Coords: ${coords}"$)
Private urlParams As Map
If ruta <> "" And almacen <> "" And coords <> "" Then
Log("|"&ayuda&"|"& req.GetParameter("h") & "|" )
If ayuda <> "" Then
resp.ContentType = "text/html"
resp.Write($"Son necesarios los siguientes parametros:<br>
* r - La ruta<br>
* a - El almacen<br>
* c - Lista de puntos (id_cliente,lon,lat) separadas por punto y coma, el primer punto de la lista, se considera el punto de INICIO de la ruta.<br>
* m - La matriz a usar LOCAL u OSRM (Opcional, default local<br>
* f - El destino final (id_cliente,lon,lat) de donde termina la ruta (Opcional)"$)
else If ruta <> "" And almacen <> "" And coords <> "" Then
If Main.final <> "" Then coords = coords & ";" & Main.final
ruta = "R" & ruta
urlParams.Initialize
@@ -77,6 +92,15 @@ Sub generaMatrizRuteoTiempos(r As String, resp As ServletResponse, ruta As Strin
Log("############################################################################")
Try
'Generamos la matriz
Private p As ResultSet = Main.db.ExecQuery($"select count(id) as cuantosPuntos from ${r}_puntos"$)
Do While p.NextRow ' Revisamos que sean MENOS de 100 puntos, si no, usamos la matriz LOCAL.
cuantosPuntos = p.GetInt("cuantosPuntos")
Loop
If cuantosPuntos > 98 Then
If matriz = "OSRM" Then Main.msg = "Mas de 100 puntos, usamos matriz LOCAL"
matriz = ""
End If
Log($"#### PUNTOS: ${cuantosPuntos}"$)
If matriz = "OSRM" Then
Wait for(Main.generaMatrizOSRM(r)) Complete (Result As Int)
Else
@@ -105,6 +129,7 @@ Sub generaMatrizRuteoTiempos(r As String, resp As ServletResponse, ruta As Strin
If matriz = "" Then tempMap.Put("api", "Local")
'Ponemos la ruta, almacen, tiempos, distancias y la lista de las coordenadas en un mapa para regresarla en un JSON.
tempMap.Put("code", "OK")
tempMap.Put("hash", elHash)
tempMap.Put("ruta", ruta)
tempMap.Put("almacen", almacen)
tempMap.Put("duration", ts.Get("duration"))
@@ -112,6 +137,7 @@ Sub generaMatrizRuteoTiempos(r As String, resp As ServletResponse, ruta As Strin
tempMap.Put("puntos", ts.Get("puntos"))
tempMap.Put("coords", listCoords)
tempMap.Put("mensaje", Main.msg)
If Main.error <> "" Then tempMap.Put("mensaje", Main.error)
If tempMap.get("puntos") = 0 Then tempMap.Put("code", "KO")
' Log(tempMap)
js.Initialize(tempMap)
@@ -132,6 +158,7 @@ Sub generaMatrizRuteoTiempos(r As String, resp As ServletResponse, ruta As Strin
tempMap.Put("puntos", 0)
tempMap.Put("coords", "")
tempMap.Put("mensaje", Main.msg)
If Main.error <> "" Then tempMap.Put("mensaje", Main.error)
' Log(tempMap)
js.Initialize(tempMap)
StopMessageLoop
@@ -156,18 +183,22 @@ Sub tiempos(r As String, resp As ServletResponse, ruta As String, almacen As Str
Private listCoords As List
listCoords.Initialize
Private coords2 As String = ""
Private coords3 As String = ""
Do While p.NextRow
listCoords.Add(CreateMap("pos":p.GetString("pos"), "id":p.GetString("id"), "lat":p.GetString("lat"), "lon":p.GetString("lon")))
If coords2 = "" Then
coords2 = $"${p.GetString("lon")},${p.GetString("lat")}"$
coords3 = $"${p.GetString("id")},${p.GetString("lon")},${p.GetString("lat")}"$
Else
coords2 = $"${coords2}:${p.GetString("lon")},${p.GetString("lat")}"$
coords3 = $"${coords3};${p.GetString("id")},${p.GetString("lon")},${p.GetString("lat")}"$
End If
Loop
Main.db.Close
tempMap.Put("api", matriz)
If matriz = "" Then tempMap.Put("api", "Local")
tempMap.Put("code", "OK")
tempMap.Put("hash", elHash)
tempMap.Put("ruta", ruta)
tempMap.Put("almacen", almacen)
tempMap.Put("duration", ts.Get("duration"))
@@ -175,6 +206,7 @@ Sub tiempos(r As String, resp As ServletResponse, ruta As String, almacen As Str
tempMap.Put("puntos", ts.Get("puntos"))
tempMap.Put("coords", listCoords)
tempMap.Put("mensaje", Main.msg)
If Main.error <> "" Then tempMap.Put("mensaje", Main.error)
If tempMap.get("puntos") = 0 Then tempMap.Put("code", "KO")
' Log(tempMap)
js.Initialize(tempMap)
@@ -184,7 +216,9 @@ Sub tiempos(r As String, resp As ServletResponse, ruta As String, almacen As Str
resp.ContentType = "text/html"
resp.Write(js.ToString)
Log("###################################################################" & CRLF)
Log($"http://keymon.lat:9001/kmz.php?a=1&c=${coords2}"$)
Log($"http://keymon.lat:${Main.srvr.port}/ga?c=${coords3}"$)
Log("###################################################################" & CRLF)
Log($"http://keymon.lat:${Main.srvr.port}/kmz.php?a=1&c=${coords2}"$)
Log("###################################################################" & CRLF)
Log("Liga para ver la ruta en mapa:" & CRLF)
Log($"https://osm.quelltextlich.at/viewer-js.html?kml_url=http://keymon.lat:9001/kmz.php?c=${coords2}"$)

37
ayuda.bas Normal file
View File

@@ -0,0 +1,37 @@
B4J=true
Group=Default Group
ModulesStructureVersion=1
Type=Class
Version=10
@EndOfDesignText@
Sub Class_Globals
Private mreq As ServletRequest 'ignore
Private mresp As ServletResponse 'ignore
End Sub
'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
End Sub
'Resumable Subs (wait for / sleep) in server handlers
'Resumable subs can only work when there is a message queue.
'By default, server handlers end when the Handle sub is completed. They do not create a message loop.
'If you want to wait for an event then you need to call StartMessageLoop and later StopMessageLoop.
'https://www.b4x.com/android/forum/threads/resumable-subs-wait-for-sleep-in-server-handlers.81833/
Sub Handle(req As ServletRequest, resp As ServletResponse)
Log("##############################################################")
Log("############# Ayuda/Handle ########################")
Log("##############################################################")
resp.ContentType = "text/html"
resp.Write($"Son necesarios los siguientes parametros:<br>
* r - La ruta<br>
* a - El almacen<br>
* c - Lista de puntos (id_cliente,lon,lat) separadas por punto y coma, el primer punto de la lista, se considera el punto de INICIO de la ruta.<br>
* m - La matriz a usar LOCAL u OSRM (Opcional, default LOCAL)<br>
* f - El destino final (id_cliente,lon,lat) de donde termina la ruta (Opcional)<br><br>
Ej.: http://localhost:${Main.srvr.port}/ruteador?m=LOCAL&r=114&a=68&c=CEDIS,-98.73952937,20.03334961;TIENDA1,-98.73952937,20.03334961<br><br>
Tambien se puede escificar el parametro <b>f</b>, que especifica que la ruta termina en ese punto.
"$)
End Sub