diff --git a/Files/LICENSE b/Files/LICENSE new file mode 100644 index 0000000..a1214f8 --- /dev/null +++ b/Files/LICENSE @@ -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. diff --git a/Files/OSMTools.js b/Files/OSMTools.js new file mode 100644 index 0000000..5d4303d --- /dev/null +++ b/Files/OSMTools.js @@ -0,0 +1,1033 @@ +if ( typeof at == "undefined" ) { at=new Object(); } +if ( typeof at.quelltextlich == "undefined" ) { at.quelltextlich=new Object(); } +at.quelltextlich.osm={ + + + + // Version information of this script + VERSION_NUMBER: "0.0.36", + RELEASE_DATE: "2022-02-16", + HOMEPAGE: "http://osm.quelltextlich.at/", + LEGAL_INFO: "http://quelltextlich.at/impressum.html", + + + + // Adds the required JavaScript tags to the document head + addJavaScript: function ( onLoadFunc ) + { + var jsNode = document.createElement('script'); + jsNode.setAttribute('type', 'text/javascript'); + jsNode.setAttribute('src', '//osm.quelltextlich.at/openlayers/2.11/OpenLayers.js'); + if ( onLoadFunc ) + { + jsNode.onreadystatechange= function () { + if ( (this.readyState == 'loaded') || (this.readyState == 'complete') ) + { + onLoadFunc(); + } + } + jsNode.onload=onLoadFunc; + } + document.getElementsByTagName('head')[0].appendChild(jsNode); + }, + + + + // Adds the CSS Stylesheet href to the document head + addStyleSheet: function ( href ) + { + var cssNode = document.createElement('link'); + cssNode.setAttribute('rel', 'stylesheet'); + cssNode.setAttribute('type', 'text/css'); + cssNode.setAttribute('href', href); + document.getElementsByTagName('head')[0].appendChild(cssNode); + }, + + + + // Yields the given text as valid, escaped xml + encodeAsXml: function ( text ) + { + return text.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }, + + + + // Gets the width and height of the viewport + getViewPortDimension: function ( ) + { + var width; + var height; + + try { + width = window.innerWidth; + height = window.innerHeight; + } catch (e) { ; } + + if ( ( typeof width == "undefined" ) || ( width <= 0 ) ) + { + try { + width = document.documentElement.clientWidth; + height = document.documentElement.clientHeight; + } catch (e) { ; } + } + + if ( ( typeof width == "undefined" ) || ( width <= 0 ) ) + { + try { + width = document.body.clientHeight; + height = document.body.clientHeight; + } catch (e) { ; } + } + + if ( ( typeof width == "undefined" ) || ( width <= 0 ) ) + { + width = 320; + height = 256; + } + return { width: width, height: height }; + }, + + + + // creates a div node with id divId. + // If a node with the given id existed beforehand, it is replaced by the newly created one. + // Otherwise, the newly created div node is inserted before siblingNode + // The created div gets width width and height height (each in Pixels). + replaceOrAddDiv: function ( divId, siblingNode, width, height ) + { + var widthIsPercent = ( ( typeof width == "string" ) && ( width.substr( width.length-1, 1 ) == "%" ) ); + var heightIsPercent = ( ( typeof height == "string" ) && ( height.substr( height.length-1, 1 ) == "%" ) ); + + if ( ( widthIsPercent ) || ( heightIsPercent ) ) + { + var viewportDim=at.quelltextlich.osm.getViewPortDimension(); + + if ( widthIsPercent ) + { + width = viewportDim.width * width.substr( 0, width.length-1 ) / 100; + } + + if ( heightIsPercent ) + { + height =viewportDim.height * height.substr( 0, height.length-1 ) / 100; + } + } + + width = Math.max( width, 170 ); + height = Math.max( height, 170 ); + + var divNode = document.createElement('div'); + divNode.setAttribute('id', divId ); + divNode.setAttribute('style', 'width: '+width+'px; height: '+height+'px;'); + + var nodeToReplace = document.getElementById( divId ); + if ( nodeToReplace == null ) + { + siblingNode.parentNode.insertBefore( divNode, siblingNode ); + } else { + nodeToReplace.parentNode.replaceChild( divNode, nodeToReplace ); + } + + return divNode; + }, + + + + // Removes 2nd, 3rd, declaration of the same CSS from + deduplicateHeadCss: function() + { + const headChildren = document.head.children; + var foundCSS = {}; + for (var idx = 0 ; idx < headChildren.length ; idx++) { + const headChild = headChildren[idx]; + if (headChild.tagName == "LINK" && headChild.type == "text/css") { + if (headChild.href in foundCSS) { + headChild.remove(); + idx--; + } else { + foundCSS[headChild.href] = true; + } + } + } + }, + + + + // Maps real world lat, lon to the coordinate system of the map + worldToMap: function ( lat, lon ) + { + var lonLat = new OpenLayers.LonLat( lon, lat ); + lonLat.transform( + new OpenLayers.Projection("EPSG:4326"), + new OpenLayers.Projection("EPSG:900913") /* This is map.getProjectionObject() */ + ); + return lonLat; + }, + + + + // Adds the maps once the OpenLayers JavaScript file is fully loaded + getText: function ( label ) + { + var i18n_text=null; + try { + i18n_text = at.quelltextlich.osm.vars['i18n'][ at.quelltextlich.osm.vars['i18n']['selectedLanguage'] ][ label ]; + } catch (e) { ; } + + if ( typeof i18n_text == "undefined" ) + { + try { + i18n_text = at.quelltextlich.osm.vars['i18n'][ at.quelltextlich.osm.vars['i18n']['defaultLanguage'] ][ label ]; + } catch (e) { ; } + } + + if ( typeof i18n_text == "undefined" ) + { + i18n_text = label; + } + return i18n_text; + }, + + + + // Sets up a basic map with controls. + // Without setting the center. + // Without markers. + addRawMap: function ( div, useZoomBar, navigation, plainMouseWheelZoom ) + { + + var map = new OpenLayers.Map( div, { controls: [] } ); + + var osmLayer = new OpenLayers.Layer.OSM(); + var navigationOptions = {}; + + osmLayer.attribution=at.quelltextlich.osm.getText( "osm.Attribution" ); + osmLayer.url=osmLayer.url.replace(/^https?:/,"") + map.addLayer( osmLayer ); + + if (plainMouseWheelZoom === false) + { + navigationOptions = {mouseWheelOptions:{keyMask:OpenLayers.Handler.MOD_CTRL}}; + } + + map.addControl(new OpenLayers.Control.Navigation(navigationOptions)); + map.addControl(new OpenLayers.Control.KeyboardDefaults()); + //map.addControl(new OpenLayers.Control.LayerSwitcher()); + map.addControl(new OpenLayers.Control.ScaleLine()); + if ( navigation !== false ) + { + if ( useZoomBar ) + { + map.addControl(new OpenLayers.Control.PanZoomBar()); + } else { + map.addControl(new OpenLayers.Control.PanZoom()); + } + } + map.addControl(new OpenLayers.Control.Attribution()); + + // This fixup allows Opera to show KML Layers in a viewer that is + // included via an element. + var containerDiv = document.getElementById(map.id+"_OpenLayers_Container"); + containerDiv.style.width="100%"; + containerDiv.style.height="100%"; + + return map; + }, + + + + // Adds a marker (specified by conf) to the marking layer layer of the map map + // The layer need not have been added to the map. + addMarking: function ( layer, conf ) { + var lon = 0; + var lat = 0; + if ( conf ) + { + if ( conf.lon ) { lon = conf.lon; } + if ( conf.lat ) { lat = conf.lat; } + } + var marker=at.quelltextlich.osm.createMarker( lat, lon ); + layer.addMarker( marker ); + return marker; + }, + + + + // Adds a marker (specified by conf) to the marking layer layer of the map map + // The layer need not have been added to the map. + getMapId: function ( map ) { + // cut away “osm_div_" from the div the map is rendered to + return map.div.id.substr(8); + }, + + + + // Destroys a created popup for a given feature + destroyPopup: function (feature) { + feature.popup.destroy(); + feature.popup = null; + }, + + + + // Creates a popup for a given feature + createPopup: function (feature) { + if ( ( typeof feature != "undefined" ) && ( typeof feature.attributes != "undefined" ) && ( typeof feature.attributes.description != "undefined" ) ) + { + var popupDescription = ''; + if ( typeof feature.attributes.name != "undefined" ) { + popupDescription += '
' + feature.attributes.name + '
'; + } + if ( ( typeof feature.attributes.description == "string" ) && ( feature.attributes.description.replace(/\s/g, '') != '' ) ) { + if ( popupDescription != '' ) { + popupDescription += '
'; + } + popupDescription += '
' + feature.attributes.description + '
'; + } + feature.popup = new OpenLayers.Popup.FramedCloud("pop", + feature.geometry.getBounds().getCenterLonLat(), + null, + popupDescription, + null, + true, + function() { at.quelltextlich.osm.destroyPopup( feature ); } + ); + feature.layer.map.addPopup(feature.popup); + } + }, + + + + // Collecting layer bounds, after a kml layer has been fully loaded + kmlLayerLoadEnded: function ( eventObj ) + { + if ( ( typeof eventObj != "undefined" ) && ( typeof eventObj.object != "undefined" ) && ( typeof eventObj.object.features != "undefined" ) ) + { + var features = eventObj.object.features; + if ( ( features.length > 0 ) && ( typeof eventObj.object.map != "undefined" ) && ( typeof eventObj.object.map != "undefined" ) ) + { + var map = eventObj.object.map; + var mapId = at.quelltextlich.osm.getMapId( map ); + var conf = at.quelltextlich.osm.vars['map'+mapId+'Conf']; + var mergedLayersBounds=at.quelltextlich.osm.vars['mergedLayersBounds_'+mapId]; + + var i; + for ( i in features ) + { + var feature = features[i]; + if ( ( typeof feature != "undefined" ) && ( feature ) && + ( typeof feature.geometry != "undefined" ) && ( feature.geometry ) && + ( typeof feature.geometry.bounds != "undefined" ) && ( feature.geometry.bounds ) ) + { + mergedLayersBounds.extend( feature.geometry.bounds ); + } + + } + + if ( typeof conf.initialPosition == "undefined" ) + { + map.zoomToExtent( mergedLayersBounds ); + + // ZoomIn+ZoomOut is Workaround for Opera + map.zoomIn(); + map.zoomOut(); + } + } + } + }, + + + + // Generates and adds a KML Layer based on the configuration + // whose url has been successfully cached on the server + addFullyCachedKMLLayer: function ( map, conf ) + { + var attribution="
"+at.quelltextlich.osm.getText('layer.kml.attribution.preLink'); + attribution+=""+ at.quelltextlich.osm.encodeAsXml( conf.attribution.text ) +""; + if ( ( conf.license.url ) || ( conf.license.name ) ) { + var addendum=at.quelltextlich.osm.getText('layer.kml.attribution.license'); + if ( conf.license.name ) { + addendum=at.quelltextlich.osm.encodeAsXml( conf.license.name ); + } + if ( conf.license.url ) { + addendum=""+ addendum +""; + } + attribution+=" ("+addendum+")"; + } + attribution+=at.quelltextlich.osm.getText('layer.kml.attribution.postLink'); + + var layer = new OpenLayers.Layer.Vector( "kml_"+conf.id, { + strategies: [new OpenLayers.Strategy.Fixed()], + protocol: new OpenLayers.Protocol.HTTP({ + url: conf.url.cache, + format: new OpenLayers.Format.KML({ + extractStyles: true, + extractAttributes: true, + maxDepth: 4 + }) + }), + eventListeners: { + loadend: at.quelltextlich.osm.kmlLayerLoadEnded + }, + attribution: attribution + }); + + map.addLayer( layer ); + + var selectorControl = new OpenLayers.Control.SelectFeature( layer, { + onSelect: at.quelltextlich.osm.createPopup, + onUnselect: at.quelltextlich.osm.destroyPopup + }); + map.addControl(selectorControl); + selectorControl.activate(); + }, + + + + // Adds the cache information to KML Configuration and adds the layer + KMLFileCached: function( httpObj, params ) + { + var errorCode=0; + var errorMessage=""; + if ( httpObj.status == 200 ) + { + var result; + try { + result = eval( "(" + httpObj.responseText + ")" ); + } catch ( e ) { + errorCode=-2; + errorMessage="Error interpreting the result of the caching service"; + } + if ( ( typeof result != "undefined" ) && ( typeof result.errorCode != "undefined" ) ) + { + errorCode=result.errorCode; + errorMessage=result.errorMessage; + if ( ( result.errorCode == 0 ) && ( typeof result.cacheUrl != "undefined" ) ) + { + params.conf.url.cache=result.cacheUrl; + at.quelltextlich.osm.addFullyCachedKMLLayer( params.map, params.conf ); + } + } + } else { + errorCode = -1; + errorMessage = "HTTP Error while communicating with cache service ("+httpObj.status+" - "+httpObj.statusText+")" + } + if ( errorCode != 0 ) + { + if ( errorMessage ) + { + alert( "File caching error:\n"+errorMessage+" (Error code: "+errorCode+")" ); + } else { + alert( "Undefined file caching error" ); + } + } + }, + + + + // Caches a URL on the Server + cacheUrlOnServer: function( url, callback, params ) + { + var httpObj = null; + try { + httpObj = new XMLHttpRequest(); + } catch(e) { + try { + httpObj = new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + try { + httpObj = new ActiveXObject("Msxml2.XMLHTTP"); + } catch(e) { + httpObj = null; + } + } + } + if (httpObj) { + var cachingUrl=at.quelltextlich.osm.vars['const']['url_caching']+encodeURIComponent( url ); + httpObj.open('GET', cachingUrl, true); + httpObj.onreadystatechange = function () { + if (httpObj.readyState == 4) { + callback( httpObj, params ); + } + }; + httpObj.send(null); + } + }, + + + + // Adds a map, along with markers, ... and centers it + addMap: function ( mapId ) + { + var conf = at.quelltextlich.osm.vars['map'+mapId+'Conf']; + + var initialLat=48.8567; + var initialLon=2.3517; + var initialZoom=11; + + var divId="osm_div_"+mapId; + + // Dimension der Karte setzen + var mapWidth=0; + var mapHeight=0; + if ( conf.dimension ) + { + if ( conf.dimension.width ) { mapWidth=conf.dimension.width; } + if ( conf.dimension.height ) { mapHeight=conf.dimension.height; } + } + var mapDiv = at.quelltextlich.osm.replaceOrAddDiv( divId, at.quelltextlich.osm.vars['map'+mapId+'OriginatingNode'], mapWidth, mapHeight ); + + var navigation = conf.navigation; + var plainMouseWheelZoom = conf.plainMouseWheelZoom; + + // Karte initialisieren + var map = at.quelltextlich.osm.addRawMap( divId, ( mapHeight >= 370 ), navigation, plainMouseWheelZoom ); + + mapDiv.className = mapDiv.className+" "+divId; + + if ( ! at.quelltextlich.osm.vars['CSSAdded'] ) + { + at.quelltextlich.osm.vars['CSSAdded']=true; + at.quelltextlich.osm.addStyleSheet( at.quelltextlich.osm.HOMEPAGE.replace(/^https?:/,"") + 'css/map.css' ); + } + + // Marker setzen + var markerLayer; + if ( conf.marker ) + { + markerLayer = new OpenLayers.Layer.Markers( "Markers" ); + + var markerConfId; + for ( markerConfId in conf.marker ) + { + var markerConf=conf.marker[markerConfId]; + at.quelltextlich.osm.addMarking( markerLayer, markerConf ); + } + + map.addLayer(markerLayer); + } + + if ( typeof conf.kmls != "undefined" ) + { + at.quelltextlich.osm.vars['mergedLayersBounds_'+mapId] = new OpenLayers.Bounds(); + for ( kmlConfId in conf.kmls ) + { + var kmlConf=conf.kmls[kmlConfId]; + + kmlConf.id=kmlConfId; + + at.quelltextlich.osm.cacheUrlOnServer( kmlConf.url.real, at.quelltextlich.osm.KMLFileCached, { conf: kmlConf, map: map } ); + + } //done adding a single kml layer + + } //done adding all kml layers + + // Ausgangsposition aus Konfiguration setzen + if ( typeof conf.initialPosition != "undefined" ) + { + if ( conf.initialPosition.lon ) { initialLon=conf.initialPosition.lon; } + if ( conf.initialPosition.lat ) { initialLat=conf.initialPosition.lat; } + if ( conf.initialPosition.zoom ) { initialZoom=conf.initialPosition.zoom; } + } + map.setCenter( at.quelltextlich.osm.worldToMap( initialLat, initialLon ), initialZoom ); + + // When adding multiple maps to a page, OpenLayers adds their + // stylesheet once for each added map. The 2nd, 3rd, ... addition of + // the OpenLayer's CSS overrides our own map.css, so we only keep the + // first variant of CSSes. + at.quelltextlich.osm.deduplicateHeadCss(); + + try { + conf.callbacks.mapAdded( mapId, map ); + } catch (e) { ; } + }, + + + + // Redirects 404s from OSM tileserver to local 404 image + monkeyPatchOpenLayersUtilonImageLoadError: function() { + var origOnImageLoadErrorFunc = OpenLayers.Util.onImageLoadError; + OpenLayers.Util.onImageLoadError = function () { + if ( /^https?:\/\/tile\.openstreetmap\.org\/-?[0-9]+\/-?[0-9]+\/-?[0-9]+\.png$/.test(this.src) ) + { + this.src="//osm.quelltextlich.at/pics/404.png"; + } else { + origOnImageLoadErrorFunc(); + } + } + }, + + + + // Patch event registration to use `passive: false` for all events. The + // default for `passive` change for a few browsers to `true` in some + // situations (E.g.: Chrome 97). There, scrolling on a map on a page would + // zoom the map /and/ scroll the page. + monkeyPatchOpenLayersEventobserve: function() { + // This method is inlined from OpenLayers 2.11. Only the call to + // addEventListener has been patched up. + OpenLayers.Event.observe = function(elementParam, name, observer, useCapture) { + var element = OpenLayers.Util.getElement(elementParam); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) { + name = 'keydown'; + } + + //if observers cache has not yet been created, create it + if (!this.observers) { + this.observers = {}; + } + + //if not already assigned, make a new unique cache ID + if (!element._eventCacheID) { + var idPrefix = "eventCacheID_"; + if (element.id) { + idPrefix = element.id + "_" + idPrefix; + } + element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix); + } + + var cacheID = element._eventCacheID; + + //if there is not yet a hash entry for this element, add one + if (!this.observers[cacheID]) { + this.observers[cacheID] = []; + } + //add a new observer to this element's list + this.observers[cacheID].push({ + 'element': element, + 'name': name, + 'observer': observer, + 'useCapture': useCapture + }); + + //add the actual browser event listener + if (element.addEventListener) { + if (OpenLayers.BROWSER_NAME != "msie") { + // Force `passive` to false to avoid zooming map and scrolling + // page at the same time. + element.addEventListener(name, observer, { + capture: useCapture, + passive: false + }); + } else { + // Fallback for MSIE, whose addEventListener does not + // support an options parameter. Since, there events are + // not passive by default, we do not run into the issue of + // both zooming and scrolling at the same time anyways. + element.addEventListener(name, observer, useCapture); + } + } else if (element.attachEvent) { + element.attachEvent('on' + name, observer); + } + } + }, + + + + // Applies all relevant monkey patches OpenLayer + monkeyPatchOpenLayers: function() { + at.quelltextlich.osm.monkeyPatchOpenLayersUtilonImageLoadError(); + at.quelltextlich.osm.monkeyPatchOpenLayersEventobserve(); + }, + + + + // Adds the maps once the OpenLayers JavaScript file is fully loaded + loaded: function () + { + if (! at.quelltextlich.osm.vars['OpenLayersFullyLoaded']) + { + at.quelltextlich.osm.vars['OpenLayersFullyLoaded']=true; + + at.quelltextlich.osm.monkeyPatchOpenLayers(); + + // add the prepared maps to the visible map + var mapCount=at.quelltextlich.osm.vars['mapCount']; + if ( mapCount ) + { + var mapId; + for ( mapId = 1; mapId <= mapCount; mapId++ ) + { + at.quelltextlich.osm.addMap( mapId ); + } + } + } + }, + + + + // Initializes the Internationalization for english + addI18nString: function ( lang, label, translation ) { + at.quelltextlich.osm.vars['i18n'][lang][label] = translation; + }, + + + + // Initializes the Internationalization for english + initI18nEn: function () { + var lang='en'; + at.quelltextlich.osm.vars['i18n'][lang] = new Object(); + addI18nString = at.quelltextlich.osm.addI18nString; + + addI18nString( lang, 'product.name', 'OSMTools Viewer' ); + addI18nString( lang, 'osm.Attribution', 'map by the OpenStreetMap contributors (CC-BY-SA 2.0)' ); + addI18nString( lang, 'openlayers.Attribution', 'OpenLayers (license)' ); + addI18nString( lang, 'layer.kml.attribution.license', 'license' ); + addI18nString( lang, 'layer.kml.attribution.preLink', 'markings by ' ); + addI18nString( lang, 'layer.kml.attribution.postLink', '' ); + addI18nString( lang, 'ui.label.close', 'Close' ); + addI18nString( lang, 'ui.label.webservice', 'Webservice' ); + addI18nString( lang, 'ui.label.version', 'Version' ); + addI18nString( lang, 'ui.label.homepage', 'Homepage' ); + addI18nString( lang, 'ui.label.legal_info', 'Legal information' ); + addI18nString( lang, 'ui.label.map', 'Map' ); + addI18nString( lang, 'ui.label.tiles', 'Tiles' ); + addI18nString( lang, 'ui.label.viewer', 'Viewer' ); + addI18nString( lang, 'ui.label.kml_overlays', 'KML Overlays' ); + addI18nString( lang, 'ui.label.kml_overlay_nr', 'KML Overlay' ); + addI18nString( lang, 'ui.label.kml_overlay.attribution', 'Attribution' ); + addI18nString( lang, 'ui.label.kml_overlay.license', 'License' ); + addI18nString( lang, 'ui.label.back_to_map', 'Return to map' ); +// addI18nString( lang, '', '' ); + }, + + + + // Initializes the Internationalization for german + initI18nDe: function initI18nDe() { + var lang='de'; + at.quelltextlich.osm.vars['i18n'][lang] = new Object(); + addI18nString = at.quelltextlich.osm.addI18nString; + + addI18nString( lang, 'product.name', 'OSMTools Betrachter' ); + addI18nString( lang, 'osm.Attribution', 'Karte von den OpenStreetMap Mitwirkenden (CC-BY-SA 2.0)' ); + addI18nString( lang, 'openlayers.Attribution', 'OpenLayers (Lizenz)' ); + addI18nString( lang, 'layer.kml.attribution.license', 'Lizenz' ); + addI18nString( lang, 'layer.kml.attribution.preLink', 'Markierungen von ' ); + addI18nString( lang, 'layer.kml.attribution.postLink', '' ); + addI18nString( lang, 'ui.label.close', 'Schließen' ); + addI18nString( lang, 'ui.label.webservice', 'Webdienst' ); + addI18nString( lang, 'ui.label.version', 'Version' ); + addI18nString( lang, 'ui.label.homepage', 'Webseite' ); + addI18nString( lang, 'ui.label.legal_info', 'Impressum' ); + addI18nString( lang, 'ui.label.map', 'Karte' ); + addI18nString( lang, 'ui.label.tiles', 'Kacheln' ); + addI18nString( lang, 'ui.label.viewer', 'Anzeige' ); + addI18nString( lang, 'ui.label.kml_overlays', 'KML Ebenen' ); + addI18nString( lang, 'ui.label.kml_overlay_nr', 'KML Ebene' ); + addI18nString( lang, 'ui.label.kml_overlay.attribution', 'Attributierung' ); + addI18nString( lang, 'ui.label.kml_overlay.license', 'Lizenz' ); + addI18nString( lang, 'ui.label.back_to_map', 'Zurück zur Karte' ); +// addI18nString( lang, '', '' ); + }, + + + + // Initializes the Internationalization for esperanto + initI18nEo: function () { + var lang='eo'; + at.quelltextlich.osm.vars['i18n'][lang] = new Object(); + addI18nString = at.quelltextlich.osm.addI18nString; + + addI18nString( lang, 'product.name', 'OSMTools Rigardilo' ); + addI18nString( lang, 'osm.Attribution', 'mapo de la OpenStreetMap kontribuantoj (CC-BY-SA 2.0)' ); + addI18nString( lang, 'openlayers.Attribution', 'OpenLayers (licenco)' ); + addI18nString( lang, 'layer.kml.attribution.license', 'permesilo' ); + addI18nString( lang, 'layer.kml.attribution.preLink', 'etikedoj de ' ); + addI18nString( lang, 'layer.kml.attribution.postLink', '' ); + addI18nString( lang, 'ui.label.close', 'Fermu' ); + addI18nString( lang, 'ui.label.webservice', 'Retservo' ); + addI18nString( lang, 'ui.label.version', 'Version' ); + addI18nString( lang, 'ui.label.homepage', 'TTT-ejo' ); + addI18nString( lang, 'ui.label.legal_info', 'Leĝa respondeco' ); + addI18nString( lang, 'ui.label.map', 'Mapo' ); + addI18nString( lang, 'ui.label.tiles', 'Platoj' ); + addI18nString( lang, 'ui.label.viewer', 'Rigardilo' ); + addI18nString( lang, 'ui.label.kml_overlays', 'KML tegoj' ); + addI18nString( lang, 'ui.label.kml_overlay_nr', 'KML tego' ); + addI18nString( lang, 'ui.label.kml_overlay.attribution', 'Atribuo' ); + addI18nString( lang, 'ui.label.kml_overlay.license', 'Permesilo' ); + addI18nString( lang, 'ui.label.back_to_map', 'Reiru al la mapo' ); +// addI18nString( lang, '', '' ); + }, + + + + // Initializes the global setting and variables + init: function () + { + at.quelltextlich.osm.vars=new Object(); + at.quelltextlich.osm.vars['preparedMaps']=new Object(); + at.quelltextlich.osm.vars['OpenLayersFullyLoaded']=false; + at.quelltextlich.osm.vars['CSSAdded']=false; + at.quelltextlich.osm.vars['mapCount']=0; + + at.quelltextlich.osm.vars['const']=new Object(); + at.quelltextlich.osm.vars['const']['url_osm']="http://www.openstreetmap.org/"; + at.quelltextlich.osm.vars['const']['url_cc_by_2_0']="http://creativecommons.org/licenses/by/2.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_3_0']="http://creativecommons.org/licenses/by/3.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nc_2_0']="http://creativecommons.org/licenses/by-nc/2.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nc_3_0']="http://creativecommons.org/licenses/by-nc/3.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nc_nd_2_0']="http://creativecommons.org/licenses/by-nc-nd/2.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nc_nd_3_0']="http://creativecommons.org/licenses/by-nc-nd/3.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nc_sa_2_0']="http://creativecommons.org/licenses/by-nc-sa/2.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nc_sa_3_0']="http://creativecommons.org/licenses/by-nc-sa/3.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nd_2_0']="http://creativecommons.org/licenses/by-nd/2.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_nd_3_0']="http://creativecommons.org/licenses/by-nd/3.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_sa_2_0']="http://creativecommons.org/licenses/by-sa/2.0/"; + at.quelltextlich.osm.vars['const']['url_cc_by_sa_3_0']="http://creativecommons.org/licenses/by-sa/3.0/"; + at.quelltextlich.osm.vars['const']['url_cc0_1_0']="http://creativecommons.org/publicdomain/zero/1.0/"; + at.quelltextlich.osm.vars['const']['url_caching']=at.quelltextlich.osm.HOMEPAGE.replace(/^https?:/,"") + "cache.json?url="; + + at.quelltextlich.osm.vars['i18n']=new Object(); + at.quelltextlich.osm.vars['i18n']['defaultLanguage']='en'; + at.quelltextlich.osm.initI18nEn(); + at.quelltextlich.osm.initI18nDe(); + at.quelltextlich.osm.initI18nEo(); + + at.quelltextlich.osm.addJavaScript( at.quelltextlich.osm.loaded ); + + if ( ! at.quelltextlich.osm.vars['i18n']['selectedLanguage'] ) + { + try { + at.quelltextlich.osm.vars['i18n']['selectedLanguage']=document.getElementsByTagName( 'html' )[0].lang; + } catch (e) { ; } + } + + }, + + + + // Gathers the required information, for setting up the map, once all + // JavaScript has been loaded + prepareMap: function ( conf ) + { + var mapId = at.quelltextlich.osm.vars['mapCount']; + if ( mapId ) + { + mapId++; + } else { + mapId=1; + } + + at.quelltextlich.osm.vars['mapCount']=mapId; + at.quelltextlich.osm.vars['map'+mapId+'Conf']=conf; + + + var scriptNodes = document.getElementsByTagName( 'script' ); + var thisScriptNode = scriptNodes[ scriptNodes.length - 1 ]; + + // Workaround for Opera Mobile, whose getElementsByTagName yields a too + // short result for the first invocation. In this case, thisScriptNode is + // one of the script tags from the tag. + if (thisScriptNode.parentNode.tagName.toUpperCase() == "HEAD") + { + // getElementsByTagName gave a script tag from within the head tag. + // This behaviour occurs on Opera Mobile for the first call to + // add a map. There, scriptNodes does /not/ contain all script + // tags, although they occur in documents.all. Hence we iterating + // manually, and thereby try to obtain the correct value for + // thisScriptNode. + + var lastScriptNode = null; + var i; + for ( i = 0 ; i < document.all.length ; i++ ) + { + node=document.all[i]; + if ( node.tagName.toUpperCase() == "SCRIPT" ) + { + lastScriptNode = node; + } + } + if ( lastScriptNode.parentNode.tagName.toUpperCase() != "HEAD" ) + { + thisScriptNode=lastScriptNode; + } + } + at.quelltextlich.osm.vars['map'+mapId+'OriginatingNode']=thisScriptNode; + + // if the OpenLayers Javascript file has been fully loaded, we directly + // start the actual addition of the map. Otherwise, the actual addition + // is carried out in at.quelltextlich.osm.loaded. + if ( at.quelltextlich.osm.vars['OpenLayersFullyLoaded'] ) + { + at.quelltextlich.osm.addMap( mapId ); + } + + }, + + + + /* + ******************************************************************** + * + * Published functions + * + ******************************************************************** + */ + + + + // Adds a map specified by a at.quelltextlich.osm.MapConfiguration object + embedMapPreconfigured: function ( conf ) + { + at.quelltextlich.osm.prepareMap( conf.conf ); + }, + + + + // Adds a map with a marker at a given position + embedMapMarkedLocation: function ( lat, lon, zoom, width, height ) + { + var conf = new at.quelltextlich.osm.MapConfiguration(); + + conf.setMapDimension( width, height ); + conf.setInitialPosition( lat, lon, zoom ); + conf.addMarker( lat, lon ); + + at.quelltextlich.osm.embedMapPreconfigured( conf ); + }, + + + + // Adds a map with a KML Layer + embedMapKML: function ( kmlUrl, width, height, kmlAttributionUrl, kmlAttribution, kmlLicenseName, kmlLicenseUrl ) + { + var conf = new at.quelltextlich.osm.MapConfiguration(); + + conf.setMapDimension( width, height ); + conf.addKML( kmlUrl, kmlAttributionUrl, kmlAttribution, kmlLicenseName, kmlLicenseUrl ); + + at.quelltextlich.osm.embedMapPreconfigured( conf ); + }, + + + + // Adds a plain map + embedMapPlain: function ( lat, lon, zoom, width, height ) + { + var conf = new at.quelltextlich.osm.MapConfiguration(); + + conf.setMapDimension( width, height ); + conf.setInitialPosition( lat, lon, zoom ); + + at.quelltextlich.osm.embedMapPreconfigured( conf ); + }, + + + + // Creates a new marker, that can be added to marking layers + createMarker: function ( lat, lon ) { + var size = new OpenLayers.Size(17,31); + var offset = new OpenLayers.Pixel(-(size.w/2), -size.h); + var icon = new OpenLayers.Icon( at.quelltextlich.osm.HOMEPAGE.replace(/^https?:/,"") + "pics/red.png", size, offset ); + + return new OpenLayers.Marker( at.quelltextlich.osm.worldToMap( lat, lon ), icon); + } + + + +}; + +// Starting the initialization +at.quelltextlich.osm.init(); + +/* + ******************************************************************** + * + * MapConfiguration + * + ******************************************************************** + */ + +at.quelltextlich.osm.MapConfiguration=function () { + + // The Object accumulation all the configuration + this.conf=new Object(); + + /* + ******************************************************************** + * + * Published functions + * + ******************************************************************** + */ + + // Sets the map's on-screen width and height + this.setMapDimension=function ( width, height ) { + if ( typeof this.conf.dimension == "undefined" ) { this.conf.dimension=new Object(); } + this.conf.dimension.width = width; + this.conf.dimension.height = height; + }; + + // Sets the default on-screet center of the map + this.setInitialPosition=function ( lat, lon, zoom ) { + if ( typeof this.conf.initialPosition == "undefined" ) { this.conf.initialPosition=new Object(); } + this.conf.initialPosition.lat = lat; + this.conf.initialPosition.lon = lon; + this.conf.initialPosition.zoom = zoom; + }; + + // Adds a marker at the given position + this.addMarker=function ( lat, lon ) { + if ( typeof this.conf.marker == "undefined" ) { this.conf.marker=new Array(); } + this.conf.marker.push( { lat: lat, lon: lon } ); + }; + + // Adds a KML file to the map + this.addKML=function ( url, attribution_url, attribution_text, license_name, license_url ) { + if ( typeof this.conf.kmls == "undefined" ) { this.conf.kmls=new Array(); } + + if ( typeof attribution_url == "undefined" ) { + attribution_url=url.replace(/^(http:\/\/[^\/]*\/).*$/,"\$1"); + } + + if ( typeof attribution_text == "undefined" ) { + attribution_text=attribution_url; + } + + if ( typeof license_name == "undefined" ) { + license_name=''; + } + + if ( typeof license_url == "undefined" ) { + license_key="url_"+license_name.replace(/[^a-zA-Z0-9]/g, "_").toLocaleLowerCase(); + if ( typeof at.quelltextlich.osm.vars['const'][license_key] == "undefined" ) { + license_url=''; + } else { + license_url=at.quelltextlich.osm.vars['const'][license_key]; + } + } + + this.conf.kmls.push( { + url: { real: url }, + attribution: { url: attribution_url, text: attribution_text }, + license: { name: license_name, url: license_url } + } ); + }; + + // Adds a KML file to the map + this.setCallbackMapAdded=function ( func ) { + if ( typeof this.conf.callbacks == "undefined" ) { this.conf.callbacks=new Object(); } + this.conf.callbacks.mapAdded = func; + }; + + // Hides navigation controls + this.hideNavigation=function ( func ) { + this.conf.navigation = false; + }; + + // Disables zooming with only mouse wheel (Zooming additionally requires Ctrl key) + this.disablePlainMouseWheelZoom=function () { + this.conf.plainMouseWheelZoom = false; + }; +}; \ No newline at end of file diff --git a/Files/README.md b/Files/README.md new file mode 100644 index 0000000..2a03c64 --- /dev/null +++ b/Files/README.md @@ -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! \ No newline at end of file diff --git a/Files/config.js b/Files/config.js new file mode 100644 index 0000000..0038772 --- /dev/null +++ b/Files/config.js @@ -0,0 +1,6 @@ +config = { + "apiKey": "AIzaSyDYUOIGHUNDHrkdsU2B-WT7loUGdqGgCfg", // REPLACE WITH YOUR_API_KEY HERE + "popSize": 128, + "numIterations": 5000, + "mutChance": 0.4, +}; \ No newline at end of file diff --git a/Files/cover.png b/Files/cover.png new file mode 100644 index 0000000..b8735d7 Binary files /dev/null and b/Files/cover.png differ diff --git a/Files/genetic-algorithm.js b/Files/genetic-algorithm.js new file mode 100644 index 0000000..2cfd833 --- /dev/null +++ b/Files/genetic-algorithm.js @@ -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) +} \ No newline at end of file diff --git a/Files/index.html b/Files/index.html new file mode 100644 index 0000000..1314b95 --- /dev/null +++ b/Files/index.html @@ -0,0 +1,51 @@ + + + Travelnetics + + + + +
+
+
Search for Waypoints
+
+
+
+ +
+
+ +
+ + + +
+
+ +
+ +
+ +
+ +
+ +
+ + + + + + + \ No newline at end of file diff --git a/Files/map.js b/Files/map.js new file mode 100644 index 0000000..a550ea4 --- /dev/null +++ b/Files/map.js @@ -0,0 +1,140 @@ +const CONVERT_TO_RADIAN_CONST = 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&libraries=places&v=weekly"; + script.defer = true; + document.body.appendChild(script); +} + +class Waypoint{ + constructor(name, location) { + this.name = name; + this.lat = location.lat(); + this.lon = location.lng(); + } +} + +function initMap() { + const map = new google.maps.Map(document.getElementById("map"), { + center: { lat: 40.749933, lng: -73.98633 }, + zoom: 13, + mapTypeControl: false, + }); + const card = document.getElementById("pac-card"); + const input = document.getElementById("pac-input"); + const biasInputElement = document.getElementById("use-location-bias"); + const strictBoundsInputElement = document.getElementById("use-strict-bounds"); + const options = { + fields: ["formatted_address", "geometry", "name"], + strictBounds: false, + }; + + // let waypoints = []; + // let waypoints = [{"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 waypoints = [{"name":"Lago Chalco 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33943361448716003,"lon":-1.73094628692019},{"name":"Lago Cuitzeo 47, Anáhuac I Secc., Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33942536954824004,"lon":-1.73091028076229},{"name":"Lago Zirahuen, 11320 Ciudad de México, CDMX, Mexico","lat":0.33936570718752,"lon":-1.73089651708991},{"name":"Lago de Patzcuaro 47, Anáhuac I Secc., Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33936884703619,"lon":-1.7308794180919003},{"name":"Lago Texcoco 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33943269993424,"lon":-1.7309953446558304},{"name":"Lago Xochimilco 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33943730935077,"lon":-1.73098643823684},{"name":"Laguna del Carmen 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33944389797152,"lon":-1.73097048592064},{"name":"Laguna Tamiahua 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33940567175386005,"lon":-1.73096375941882},{"name":"Laguna San Cristóbal 47, Anáhuac I Secc., Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33937060807416003,"lon":-1.73095079336225},{"name":"Lago Viesca, Anáhuac I Secc, 11320 Ciudad de México, CDMX, Mexico","lat":0.33936519406050003,"lon":-1.7309738578982001},{"name":"Lago Tlahualilo 47, Anáhuac I Secc, Miguel Hidalgo, 11320 Ciudad de México, CDMX, Mexico","lat":0.33935459292608006,"lon":-1.7309583366785102},{"name":"Laguna de Términos, Anáhuac I Secc, Ciudad de México, CDMX, Mexico","lat":0.33934515767210005,"lon":-1.7309961736875799}]; + + // let waypoints = [{"name":"CEDIS","lon":-98.73952937,"lat":20.03334961},{"name":"CEDIS2","lon":-98.73952937,"lat":20.03334961},{"name":"114062","lon":-98.44958800,"lat":20.15207200},{"name":"114063","lon":-98.44478000,"lat":20.15266200},{"name":"114064","lon":-98.43221300,"lat":20.15774100},{"name":"114065","lon":-98.43966600,"lat":20.14535100},{"name":"114066","lon":-98.43899000,"lat":20.14397300},{"name":"114069","lon":-98.43652400,"lat":20.14219800},{"name":"114070","lon":-98.43183200,"lat":20.13570600},{"name":"114146","lon":-98.43132600,"lat":20.15803900},{"name":"114273","lon":-98.42170700,"lat":20.12253100},{"name":"114281","lon":-98.38493300,"lat":20.16163100},{"name":"115012","lon":-98.43156200,"lat":20.11126600},{"name":"115013","lon":-98.43105700,"lat":20.11014500},{"name":"115152","lon":-98.43225200,"lat":20.11085800},{"name":"115548","lon":-98.40112200,"lat":20.11897300},{"name":"355801","lon":-98.43722167,"lat":20.14291500},{"name":"355812","lon":-98.43856000,"lat":20.14605500},{"name":"355839","lon":-98.43425667,"lat":20.15194500},{"name":"355840","lon":-98.44034333,"lat":20.14676000},{"name":"355841","lon":-98.43548000,"lat":20.14006000},{"name":"355976","lon":-98.41779000,"lat":20.11532667},{"name":"356149","lon":-98.38653500,"lat":20.17235500},{"name":"356823","lon":-98.43020070,"lat":20.13345172},{"name":"356832","lon":-98.42951500,"lat":20.13281833},{"name":"ESE02002","lon":-98.414308,"lat":20.1186904},{"name":"ESE02004","lon":-98.4117485,"lat":20.1119873},{"name":"ESE02023","lon":-98.429918,"lat":20.1094994},{"name":"ESE02025","lon":-98.4319765,"lat":20.1105427},{"name":"ESE02027","lon":-98.4095106,"lat":20.11175},{"name":"ESE02028","lon":-98.416737,"lat":20.1068705}]; + // let waypoints = [{"name":"ESE05002","lon":-98.367397,"lat":20.2885251},{"name":"ESE05003","lon":-98.3674382,"lat":20.287592},{"name":"ESE05004","lon":-98.3674382,"lat":20.2879142},{"name":"ESE05005","lon":-98.3304542,"lat":20.2411999},{"name":"ESE05006","lon":-98.3674147,"lat":20.2631807},{"name":"ESE05007","lon":-98.348528,"lat":20.2820715},{"name":"ESE05008","lon":-98.3981838,"lat":20.1528387},{"name":"ESE05009","lon":-98.4158139,"lat":20.1541421},{"name":"ESE05010","lon":-98.410603,"lat":20.1551811},{"name":"ESE05011","lon":-98.4105995,"lat":20.1582812},{"name":"ESE05012","lon":-98.3674102,"lat":20.160304},{"name":"ESE05013","lon":-98.2018217,"lat":20.157312},{"name":"ESE05014","lon":-98.3986541,"lat":20.1600943},{"name":"ESE05015","lon":-98.3717737,"lat":20.0960621},{"name":"ESE05016","lon":-98.3733305,"lat":20.0934303},{"name":"ESE05017","lon":-98.354654,"lat":20.0976982},{"name":"ESE05018","lon":-98.3543659,"lat":20.097339},{"name":"ESE05019","lon":-98.3578046,"lat":20.0983058},{"name":"ESE05020","lon":-98.3597017,"lat":20.1002531},{"name":"ESE05021","lon":-98.359305,"lat":20.1076649},{"name":"ESE05022","lon":-98.367437,"lat":20.0989306},{"name":"TSE0555000","lon":-98.3683624,"lat":20.097075},{"name":"115683","lon":-98.3744864,"lat":20.0959816},{"name":"115685","lon":-98.3746171,"lat":20.0937065},{"name":"115686","lon":-98.3717499,"lat":20.0904887},{"name":"115687","lon":-98.4171454,"lat":20.0950241},{"name":"115688","lon":-98.4171373,"lat":20.0951271},{"name":"115690","lon":-98.4171953,"lat":20.0969398},{"name":"115693","lon":-98.3729919,"lat":20.1005734},{"name":"115694","lon":-98.3732723,"lat":20.0958825},{"name":"115695","lon":-98.4171635,"lat":20.0934635},{"name":"115696","lon":-98.4171583,"lat":20.1079494},{"name":"115697","lon":-98.35414,"lat":20.106525},{"name":"115699","lon":-98.355117,"lat":20.1066587},{"name":"115701","lon":-98.3575839,"lat":20.0997116},{"name":"115702","lon":-98.3575733,"lat":20.102067},{"name":"115703","lon":-98.3603581,"lat":20.0989735},{"name":"115707","lon":-98.3600443,"lat":20.1044867},{"name":"115712","lon":-98.3571027,"lat":20.102067},{"name":"115713","lon":-98.3602375,"lat":20.1011948},{"name":"115714","lon":-98.360066,"lat":20.1044785},{"name":"115715","lon":-98.3674397,"lat":20.1069279},{"name":"115717","lon":-98.3595572,"lat":20.1072504},{"name":"115718","lon":-98.3568694,"lat":20.1071554},{"name":"115720","lon":-98.3470749,"lat":20.2869986},{"name":"115721","lon":-98.3580592,"lat":20.2664201},{"name":"115722","lon":-98.3674329,"lat":20.2875573},{"name":"115723","lon":-98.4171617,"lat":20.2870722},{"name":"115725","lon":-98.3473113,"lat":20.285622},{"name":"115726","lon":-98.3485247,"lat":20.2856734},{"name":"115728","lon":-98.4171617,"lat":20.2875307},{"name":"115734","lon":-98.3673448,"lat":20.2845417},{"name":"115740","lon":-98.5307409,"lat":20.0950772},{"name":"115741","lon":-98.5313826,"lat":20.0950772},{"name":"115742","lon":-98.5311545,"lat":20.0950977},{"name":"115743","lon":-98.3092364,"lat":20.0295646},{"name":"115744","lon":-98.2993573,"lat":19.9890079},{"name":"115745","lon":-98.3001282,"lat":20.0374151},{"name":"115746","lon":-98.2998736,"lat":20.0373747},{"name":"115747","lon":-98.3113997,"lat":20.0373747},{"name":"115748","lon":-98.3048551,"lat":20.0373969},{"name":"115749","lon":-98.3981877,"lat":20.1611756},{"name":"115750","lon":-98.3981638,"lat":20.1611726},{"name":"115751","lon":-98.2033956,"lat":20.1597869},{"name":"115752","lon":-98.3981603,"lat":20.1599041},{"name":"115753","lon":-98.3675814,"lat":20.1600466},{"name":"115754","lon":-98.3981804,"lat":20.1613062},{"name":"115755","lon":-98.3675814,"lat":20.157501},{"name":"115756","lon":-98.2037883,"lat":20.1579131},{"name":"115757","lon":-98.4018328,"lat":20.1582555},{"name":"115760","lon":-98.4017017,"lat":20.1586705},{"name":"115761","lon":-98.2037471,"lat":20.1600194},{"name":"115764","lon":-98.5316151,"lat":20.0950928},{"name":"115765","lon":-98.4731448,"lat":20.0950862},{"name":"115767","lon":-98.4793191,"lat":20.0950853},{"name":"115768","lon":-98.4185455,"lat":20.1598054},{"name":"115769","lon":-98.4773212,"lat":20.0950898},{"name":"115770","lon":-98.4782102,"lat":20.0689478},{"name":"115771","lon":-98.5117017,"lat":20.0950945},{"name":"115772","lon":-98.5321791,"lat":20.0950872},{"name":"115773","lon":-98.5310337,"lat":20.0880191},{"name":"115774","lon":-98.4797164,"lat":20.0950921},{"name":"115775","lon":-98.4954019,"lat":20.0950948},{"name":"115776","lon":-98.4964301,"lat":20.095097},{"name":"115778","lon":-98.2974103,"lat":20.0382089},{"name":"115779","lon":-98.2974578,"lat":20.0382999},{"name":"115780","lon":-98.2997214,"lat":20.0191883},{"name":"115781","lon":-98.2965265,"lat":20.0374267},{"name":"115782","lon":-98.3096702,"lat":20.0382294},{"name":"115783","lon":-98.3020753,"lat":20.0281433},{"name":"115784","lon":-98.3097373,"lat":20.0378},{"name":"115786","lon":-98.3097373,"lat":20.03822},{"name":"115787","lon":-98.3024263,"lat":20.0382022},{"name":"115788","lon":-98.3024866,"lat":20.0381959},{"name":"115789","lon":-98.2998506,"lat":20.0373507},{"name":"115792","lon":-98.3098507,"lat":20.0382032},{"name":"115793","lon":-98.3019379,"lat":20.0373344},{"name":"115794","lon":-98.3098262,"lat":20.0382301},{"name":"115795","lon":-98.2994654,"lat":20.0191821},{"name":"115797","lon":-98.4171682,"lat":20.0969286},{"name":"115799","lon":-98.3748629,"lat":20.0952055},{"name":"115803","lon":-98.3739688,"lat":20.0950052},{"name":"115843","lon":-98.3087633,"lat":20.0373588},{"name":"115844","lon":-98.3098019,"lat":20.0382226},{"name":"115845","lon":-98.309962,"lat":20.0382169},{"name":"115847","lon":-98.3097854,"lat":20.0378621},{"name":"115849","lon":-98.3547467,"lat":20.1085665},{"name":"115850","lon":-98.3571229,"lat":20.106387},{"name":"115851","lon":-98.3569592,"lat":20.1102607},{"name":"115852","lon":-98.3011024,"lat":20.0382249},{"name":"115853","lon":-98.3545391,"lat":20.0765822},{"name":"115854","lon":-98.310657,"lat":20.0382241},{"name":"115860","lon":-98.4175739,"lat":20.1604813},{"name":"115864","lon":-98.5311341,"lat":20.0950882},{"name":"357004","lon":-98.3472617,"lat":20.2878516},{"name":"357005","lon":-98.3485252,"lat":20.2840998},{"name":"357006","lon":-98.3674328,"lat":20.2380154},{"name":"357007","lon":-98.3673986,"lat":20.2842333},{"name":"357089","lon":-98.4105797,"lat":20.1604658},{"name":"357090","lon":-98.4106149,"lat":20.1601633},{"name":"357091","lon":-98.4105988,"lat":20.1604658},{"name":"357092","lon":-98.4015936,"lat":20.1598066},{"name":"357093","lon":-98.4171777,"lat":20.1600988},{"name":"357094","lon":-98.4171707,"lat":20.1586434},{"name":"357195","lon":-98.5316393,"lat":20.0950869},{"name":"357196","lon":-98.4821405,"lat":20.0794694},{"name":"357305","lon":-98.3091585,"lat":20.038213},{"name":"357306","lon":-98.3091305,"lat":20.0382337},{"name":"357307","lon":-98.3674109,"lat":20.0883744},{"name":"357308","lon":-98.3674448,"lat":20.0883075},{"name":"357309","lon":-98.3047418,"lat":20.0372789},{"name":"357651","lon":-98.4171079,"lat":20.0942}]; + let waypoints = [{"pos":"0","lon":-98.367397,"name":"_ESE05002","lat":20.2885251},{"pos":"1","lon":-98.3673986,"name":"_357007","lat":20.2842333},{"pos":"2","lon":-98.4171617,"name":"_115728","lat":20.2875307},{"pos":"3","lon":-98.4171617,"name":"_115723","lat":20.2870722},{"pos":"4","lon":-98.2018217,"name":"_ESE05013","lat":20.157312},{"pos":"5","lon":-98.2037883,"name":"_115756","lat":20.1579131},{"pos":"6","lon":-98.2033956,"name":"_115751","lat":20.1597869},{"pos":"7","lon":-98.2037471,"name":"_115761","lat":20.1600194},{"pos":"8","lon":-98.3304542,"name":"_ESE05005","lat":20.2411999},{"pos":"9","lon":-98.3675814,"name":"_115755","lat":20.157501},{"pos":"10","lon":-98.3981804,"name":"_115754","lat":20.1613062},{"pos":"11","lon":-98.3981877,"name":"_115749","lat":20.1611756},{"pos":"12","lon":-98.3981638,"name":"_115750","lat":20.1611726},{"pos":"13","lon":-98.3981838,"name":"_ESE05008","lat":20.1528387},{"pos":"14","lon":-98.4105988,"name":"_357091","lat":20.1604658},{"pos":"15","lon":-98.4105797,"name":"_357089","lat":20.1604658},{"pos":"16","lon":-98.4106149,"name":"_357090","lat":20.1601633},{"pos":"17","lon":-98.4105995,"name":"_ESE05011","lat":20.1582812},{"pos":"18","lon":-98.4171635,"name":"_115695","lat":20.0934635},{"pos":"19","lon":-98.3746171,"name":"_115685","lat":20.0937065},{"pos":"20","lon":-98.3113997,"name":"_115747","lat":20.0373747},{"pos":"21","lon":-98.3097373,"name":"_115784","lat":20.0378},{"pos":"22","lon":-98.3097854,"name":"_115847","lat":20.0378621},{"pos":"23","lon":-98.3097373,"name":"_115786","lat":20.03822},{"pos":"24","lon":-98.3091305,"name":"_357306","lat":20.0382337},{"pos":"25","lon":-98.3092364,"name":"_115743","lat":20.0295646},{"pos":"26","lon":-98.3020753,"name":"_115783","lat":20.0281433},{"pos":"27","lon":-98.2993573,"name":"_115744","lat":19.9890079},{"pos":"28","lon":-98.2994654,"name":"_115795","lat":20.0191821},{"pos":"29","lon":-98.2997214,"name":"_115780","lat":20.0191883},{"pos":"30","lon":-98.2998736,"name":"_115746","lat":20.0373747},{"pos":"31","lon":-98.2965265,"name":"_115781","lat":20.0374267},{"pos":"32","lon":-98.2974103,"name":"_115778","lat":20.0382089},{"pos":"33","lon":-98.2974578,"name":"_115779","lat":20.0382999},{"pos":"34","lon":-98.2998506,"name":"_115789","lat":20.0373507},{"pos":"35","lon":-98.3001282,"name":"_115745","lat":20.0374151},{"pos":"36","lon":-98.3011024,"name":"_115852","lat":20.0382249},{"pos":"37","lon":-98.3019379,"name":"_115793","lat":20.0373344},{"pos":"38","lon":-98.3024263,"name":"_115787","lat":20.0382022},{"pos":"39","lon":-98.3024866,"name":"_115788","lat":20.0381959},{"pos":"40","lon":-98.3047418,"name":"_357309","lat":20.0372789},{"pos":"41","lon":-98.3048551,"name":"_115748","lat":20.0373969},{"pos":"42","lon":-98.3087633,"name":"_115843","lat":20.0373588},{"pos":"43","lon":-98.3091585,"name":"_357305","lat":20.038213},{"pos":"44","lon":-98.3096702,"name":"_115782","lat":20.0382294},{"pos":"45","lon":-98.3098262,"name":"_115794","lat":20.0382301},{"pos":"46","lon":-98.3098019,"name":"_115844","lat":20.0382226},{"pos":"47","lon":-98.3098507,"name":"_115792","lat":20.0382032},{"pos":"48","lon":-98.309962,"name":"_115845","lat":20.0382169},{"pos":"49","lon":-98.310657,"name":"_115854","lat":20.0382241},{"pos":"50","lon":-98.3545391,"name":"_115853","lat":20.0765822},{"pos":"51","lon":-98.3674448,"name":"_357308","lat":20.0883075},{"pos":"52","lon":-98.3674109,"name":"_357307","lat":20.0883744},{"pos":"53","lon":-98.3717499,"name":"_115686","lat":20.0904887},{"pos":"54","lon":-98.3733305,"name":"_ESE05016","lat":20.0934303},{"pos":"55","lon":-98.3729919,"name":"_115693","lat":20.1005734},{"pos":"56","lon":-98.3674397,"name":"_115715","lat":20.1069279},{"pos":"57","lon":-98.3597017,"name":"_ESE05020","lat":20.1002531},{"pos":"58","lon":-98.3603581,"name":"_115703","lat":20.0989735},{"pos":"59","lon":-98.3578046,"name":"_ESE05019","lat":20.0983058},{"pos":"60","lon":-98.3575839,"name":"_115701","lat":20.0997116},{"pos":"61","lon":-98.3571027,"name":"_115712","lat":20.102067},{"pos":"62","lon":-98.3575733,"name":"_115702","lat":20.102067},{"pos":"63","lon":-98.35414,"name":"_115697","lat":20.106525},{"pos":"64","lon":-98.3547467,"name":"_115849","lat":20.1085665},{"pos":"65","lon":-98.3569592,"name":"_115851","lat":20.1102607},{"pos":"66","lon":-98.3543659,"name":"_ESE05018","lat":20.097339},{"pos":"67","lon":-98.354654,"name":"_ESE05017","lat":20.0976982},{"pos":"68","lon":-98.355117,"name":"_115699","lat":20.1066587},{"pos":"69","lon":-98.3568694,"name":"_115718","lat":20.1071554},{"pos":"70","lon":-98.3571229,"name":"_115850","lat":20.106387},{"pos":"71","lon":-98.3602375,"name":"_115713","lat":20.1011948},{"pos":"72","lon":-98.359305,"name":"_ESE05021","lat":20.1076649},{"pos":"73","lon":-98.3595572,"name":"_115717","lat":20.1072504},{"pos":"74","lon":-98.3600443,"name":"_115707","lat":20.1044867},{"pos":"75","lon":-98.360066,"name":"_115714","lat":20.1044785},{"pos":"76","lon":-98.367437,"name":"_ESE05022","lat":20.0989306},{"pos":"77","lon":-98.3683624,"name":"_TSE0555000","lat":20.097075},{"pos":"78","lon":-98.3717737,"name":"_ESE05015","lat":20.0960621},{"pos":"79","lon":-98.3732723,"name":"_115694","lat":20.0958825},{"pos":"80","lon":-98.3739688,"name":"_115803","lat":20.0950052},{"pos":"81","lon":-98.3744864,"name":"_115683","lat":20.0959816},{"pos":"82","lon":-98.3748629,"name":"_115799","lat":20.0952055},{"pos":"83","lon":-98.4171079,"name":"_357651","lat":20.0942},{"pos":"84","lon":-98.4171454,"name":"_115687","lat":20.0950241},{"pos":"85","lon":-98.4171373,"name":"_115688","lat":20.0951271},{"pos":"86","lon":-98.4171682,"name":"_115797","lat":20.0969286},{"pos":"87","lon":-98.4782102,"name":"_115770","lat":20.0689478},{"pos":"88","lon":-98.4821405,"name":"_357196","lat":20.0794694},{"pos":"89","lon":-98.5321791,"name":"_115772","lat":20.0950872},{"pos":"90","lon":-98.5316393,"name":"_357195","lat":20.0950869},{"pos":"91","lon":-98.5316151,"name":"_115764","lat":20.0950928},{"pos":"92","lon":-98.5313826,"name":"_115741","lat":20.0950772},{"pos":"93","lon":-98.5311545,"name":"_115742","lat":20.0950977},{"pos":"94","lon":-98.5311341,"name":"_115864","lat":20.0950882},{"pos":"95","lon":-98.5307409,"name":"_115740","lat":20.0950772},{"pos":"96","lon":-98.5310337,"name":"_115773","lat":20.0880191},{"pos":"97","lon":-98.5117017,"name":"_115771","lat":20.0950945},{"pos":"98","lon":-98.4964301,"name":"_115776","lat":20.095097},{"pos":"99","lon":-98.4954019,"name":"_115775","lat":20.0950948},{"pos":"100","lon":-98.4797164,"name":"_115774","lat":20.0950921},{"pos":"101","lon":-98.4793191,"name":"_115767","lat":20.0950853},{"pos":"102","lon":-98.4773212,"name":"_115769","lat":20.0950898},{"pos":"103","lon":-98.4731448,"name":"_115765","lat":20.0950862},{"pos":"104","lon":-98.4171953,"name":"_115690","lat":20.0969398},{"pos":"105","lon":-98.4171583,"name":"_115696","lat":20.1079494},{"pos":"106","lon":-98.4158139,"name":"_ESE05009","lat":20.1541421},{"pos":"107","lon":-98.4185455,"name":"_115768","lat":20.1598054},{"pos":"108","lon":-98.4175739,"name":"_115860","lat":20.1604813},{"pos":"109","lon":-98.4171777,"name":"_357093","lat":20.1600988},{"pos":"110","lon":-98.4171707,"name":"_357094","lat":20.1586434},{"pos":"111","lon":-98.410603,"name":"_ESE05010","lat":20.1551811},{"pos":"112","lon":-98.4018328,"name":"_115757","lat":20.1582555},{"pos":"113","lon":-98.4017017,"name":"_115760","lat":20.1586705},{"pos":"114","lon":-98.4015936,"name":"_357092","lat":20.1598066},{"pos":"115","lon":-98.3986541,"name":"_ESE05014","lat":20.1600943},{"pos":"116","lon":-98.3981603,"name":"_115752","lat":20.1599041},{"pos":"117","lon":-98.3675814,"name":"_115753","lat":20.1600466},{"pos":"118","lon":-98.3674102,"name":"_ESE05012","lat":20.160304},{"pos":"119","lon":-98.3674328,"name":"_357006","lat":20.2380154},{"pos":"120","lon":-98.3674147,"name":"_ESE05006","lat":20.2631807},{"pos":"121","lon":-98.3580592,"name":"_115721","lat":20.2664201},{"pos":"122","lon":-98.348528,"name":"_ESE05007","lat":20.2820715},{"pos":"123","lon":-98.3472617,"name":"_357004","lat":20.2878516},{"pos":"124","lon":-98.3470749,"name":"_115720","lat":20.2869986},{"pos":"125","lon":-98.3473113,"name":"_115725","lat":20.285622},{"pos":"126","lon":-98.3485247,"name":"_115726","lat":20.2856734},{"pos":"127","lon":-98.3485252,"name":"_357005","lat":20.2840998},{"pos":"128","lon":-98.3673448,"name":"_115734","lat":20.2845417},{"pos":"129","lon":-98.3674329,"name":"_115722","lat":20.2875573},{"pos":"130","lon":-98.3674382,"name":"_ESE05003","lat":20.287592},{"pos":"131","lon":-98.3674382,"name":"_ESE05004","lat":20.2879142}]; + + function showResult() { + if (waypoints.length < 2) { + alert("Please enter at least 2 waypoints!"); + } + else { + let returnToOrigin = document.querySelector("#return-to-origin").checked; + localStorage.setItem("waypoints", JSON.stringify(waypoints)); + localStorage.setItem("returnToOrigin", returnToOrigin); + window.location.href = "result.html"; + } + } + document.getElementById("goto-result").addEventListener("click", showResult); + + function deleteWaypoint(currentElement, waypoint) { + waypoints.splice(waypoints.indexOf(waypoint), 1); + currentElement.closest("ul").removeChild(currentElement.parentNode); + } + + let waypointsList = document.getElementById("waypoints-list"); + + map.controls[google.maps.ControlPosition.TOP_LEFT].push(card); + + const autocomplete = new google.maps.places.Autocomplete(input, options); + + // Bind the map's bounds (viewport) property to the autocomplete object, + // so that the autocomplete requests use the current map bounds for the + // bounds option in the request. + autocomplete.bindTo("bounds", map); + + const infowindow = new google.maps.InfoWindow(); + const infowindowContent = document.getElementById("infowindow-content"); + + infowindow.setContent(infowindowContent); + + const marker = new google.maps.Marker({ + map, + anchorPoint: new google.maps.Point(0, -29), + }); + + autocomplete.addListener("place_changed", () => { + infowindow.close(); + marker.setVisible(false); + + const place = autocomplete.getPlace(); + let waypoint = new Waypoint(place.formatted_address, place.geometry.location); + + // Add a waypoint to the waypoints list + let waypointElement = document.createElement("li"); + waypointElement.append(place.name); + let deleteButton = document.createElement("button"); + deleteButton.innerHTML = "X"; + deleteButton.addEventListener("click", function(event) { + deleteWaypoint(event.currentTarget, waypoint); + }); + deleteButton.setAttribute("style", "border-radius: 70%; height: 30px; width: 30px; color: #FFFFFF; background-color: #FF0000;\ + margin-left: 20px; border: none;"); + waypointElement.appendChild(deleteButton); + waypointsList.appendChild(waypointElement); + // Create a ul to store the waypoint information + let waypointElementList = document.createElement("ul"); + waypointElement.appendChild(waypointElementList); + let waypointAddress = document.createElement("li"); + let waypointLat = document.createElement("li"); + let waypointLon = document.createElement("li"); + waypointAddress.appendChild(document.createTextNode("Full Address: " + waypoint.name)); + waypointLat.appendChild(document.createTextNode("Latitude: " + waypoint.lat)); + waypointLon.appendChild(document.createTextNode("Longtitude: " + waypoint.lon)); + waypointElementList.appendChild(waypointAddress); + waypointElementList.appendChild(waypointLat); + waypointElementList.appendChild(waypointLon); + + // Convert lat and lon to radians + waypoint.lat *= CONVERT_TO_RADIAN_CONST; + waypoint.lon *= CONVERT_TO_RADIAN_CONST; + waypoints.push(waypoint); + + if (!place.geometry || !place.geometry.location) { + // User entered the name of a Place that was not suggested and + // pressed the Enter key, or the Place Details request failed. + window.alert("No details available for input: '" + place.name + "'"); + return; + } + + // If the place has a geometry, then present it on a map. + if (place.geometry.viewport) { + map.fitBounds(place.geometry.viewport); + } else { + map.setCenter(place.geometry.location); + map.setZoom(17); + } + + marker.setPosition(place.geometry.location); + marker.setVisible(true); + infowindowContent.children["place-name"].textContent = place.name; + infowindowContent.children["place-address"].textContent = + place.formatted_address; + infowindow.open(map, marker); + }); +} \ No newline at end of file diff --git a/Files/osm.html b/Files/osm.html new file mode 100644 index 0000000..5b6060b --- /dev/null +++ b/Files/osm.html @@ -0,0 +1,9 @@ + + + + + +test + \ No newline at end of file diff --git a/Files/r2.html b/Files/r2.html new file mode 100644 index 0000000..8230bbe --- /dev/null +++ b/Files/r2.html @@ -0,0 +1,24 @@ + + + Travelnetics + + + +
+

Results:

+
    +
+ +
+ +
+ + + + + + + \ No newline at end of file diff --git a/Files/result.html b/Files/result.html new file mode 100644 index 0000000..8230bbe --- /dev/null +++ b/Files/result.html @@ -0,0 +1,24 @@ + + + Travelnetics + + + +
+

Results:

+
    +
+ +
+ +
+ + + + + + + \ No newline at end of file diff --git a/Files/style.css b/Files/style.css new file mode 100644 index 0000000..42df65b --- /dev/null +++ b/Files/style.css @@ -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; + } \ No newline at end of file diff --git a/Genetic_Algorithm.bas b/Genetic_Algorithm.bas new file mode 100644 index 0000000..5f9f909 --- /dev/null +++ b/Genetic_Algorithm.bas @@ -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}
"$) +' Log(l0) +' For i = 0 To 10 +' resp.Write($"${ShuffleList(l0)}
"$) + '' 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($"# LA SOLUCION ES:
${CRLF}${laSolucion}
"$) + resp.Write($"# PUNTOS: ${laSolucion.individuo.Size}
"$) + 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($"# DISTANCIA ORIGINAL: ${NumberFormat2(laDist, 1, 2, 2, True)}
"$) + + 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($"# DISTANCIA MEJORADA: ${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($"
# FITNESS : ${CRLF}${NumberFormat2(laSolucion.fitness, 1, 2, 2, True)}"$) + resp.Write($"
# TIEMPO : ${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 \ No newline at end of file diff --git a/Genetic_Algorythm.bas b/Genetic_Algorythm.bas new file mode 100644 index 0000000..c994e85 --- /dev/null +++ b/Genetic_Algorythm.bas @@ -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 diff --git a/Ruteador-NonUI.b4j b/Ruteador-NonUI.b4j index e46fd30..7cf4670 100644 --- a/Ruteador-NonUI.b4j +++ b/Ruteador-NonUI.b4j @@ -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 diff --git a/Ruteador-NonUI.b4j.meta b/Ruteador-NonUI.b4j.meta index f871eaf..9d5a4b0 100644 --- a/Ruteador-NonUI.b4j.meta +++ b/Ruteador-NonUI.b4j.meta @@ -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 diff --git a/Ruteador.bas b/Ruteador.bas index c820927..29cd124 100644 --- a/Ruteador.bas +++ b/Ruteador.bas @@ -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:
+ * r - La ruta
+ * a - El almacen
+ * 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.
+ * m - La matriz a usar LOCAL u OSRM (Opcional, default local
+ * 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}"$) diff --git a/ayuda.bas b/ayuda.bas new file mode 100644 index 0000000..6a74852 --- /dev/null +++ b/ayuda.bas @@ -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:
+ * r - La ruta
+ * a - El almacen
+ * 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.
+ * m - La matriz a usar LOCAL u OSRM (Opcional, default LOCAL)
+ * f - El destino final (id_cliente,lon,lat) de donde termina la ruta (Opcional)

+ Ej.: http://localhost:${Main.srvr.port}/ruteador?m=LOCAL&r=114&a=68&c=CEDIS,-98.73952937,20.03334961;TIENDA1,-98.73952937,20.03334961

+ Tambien se puede escificar el parametro f, que especifica que la ruta termina en ese punto. + + "$) +End Sub \ No newline at end of file