├── .babelrc ├── .gitignore ├── LICENSE ├── package.json ├── js ├── tests │ └── twobustests.js ├── userinput.js ├── app.js ├── displayroutes.js ├── router.js └── buses.js ├── README.md ├── index.html └── bundle.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node.js 2 | # 3 | node_modules/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Janith Leanage 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BusRouteJS", 3 | "version": "3.0.0", 4 | "description": "A JS rewrite of the original bus route finder", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/janithl/BusRouteJS.git" 8 | }, 9 | "license": "MIT", 10 | "homepage": "https://github.com/janithl/BusRouteJS", 11 | "main": "js/app.js", 12 | "dependencies": { 13 | "react": "^15.0.1", 14 | "react-dom": "^15.0.1", 15 | "babel-preset-es2015": "^6.6.0", 16 | "babel-preset-react": "^6.5.0", 17 | "babelify": "^7.2.0", 18 | "browserify": "^11.0.1", 19 | "browserify-shim": "^3.8.12", 20 | "watchify": "^3.4.0" 21 | }, 22 | "scripts": { 23 | "build": "browserify js/app.js -t babelify -t browserify-shim | uglifyjs > bundle.js", 24 | "start": "watchify js/app.js -v -t babelify -t browserify-shim -o bundle.js", 25 | "test": "jest" 26 | }, 27 | "jest": { 28 | "testRegex": "js/tests/.*\\.js$" 29 | }, 30 | "browserify-shim": { 31 | "react": "global:React", 32 | "react-dom": "global:ReactDOM" 33 | }, 34 | "devDependencies": { 35 | "babel-jest": "^14.0.0", 36 | "babel-polyfill": "^6.9.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /js/tests/twobustests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | jest.unmock('../router.js'); 4 | 5 | import Router from '../router'; 6 | var router = new Router(); 7 | 8 | const Nugegoda = "45"; 9 | const Pepiliyana = "233"; 10 | const Kohuwala = "231"; 11 | 12 | describe('test2Route', () => { 13 | var fromRoutes = router.findStopRoutes(Nugegoda); 14 | it('Routes from Nugegoda contains 163', () => { 15 | expect(fromRoutes).toContain("28"); 16 | }); 17 | 18 | it('Routes from Nugegoda contains 176', () => { 19 | expect(fromRoutes).toContain("29"); 20 | }); 21 | 22 | var toRoutes = router.findStopRoutes(Pepiliyana); 23 | 24 | it('Routes from Pepiliyana contains 120s', () => { 25 | expect(toRoutes).toContain("12"); 26 | expect(toRoutes).toContain("13"); 27 | expect(toRoutes).toContain("14"); 28 | }); 29 | 30 | var fromStops = [], toStops = []; 31 | fromRoutes.forEach(function(fr) { 32 | fromStops = fromStops.concat(router.findReachableStops(fr, Nugegoda)); 33 | }); 34 | 35 | toRoutes.forEach(function(tr) { 36 | toStops = toStops.concat(router.findReachableStops(tr, Pepiliyana)); 37 | }); 38 | 39 | it('Kohuwala is reachable from Nugegoda', () => { 40 | expect(fromStops).toContain(Kohuwala); 41 | }); 42 | 43 | it('Kohuwala is reachable from Pepiliyana', () => { 44 | expect(toStops).toContain(Kohuwala); 45 | }); 46 | 47 | var common = router.intersect(fromStops, toStops); 48 | it('Kohuwala is reachable from both', () => { 49 | expect(common).toContain(Kohuwala); 50 | }); 51 | 52 | var routes = router.findRoutes(Nugegoda, Pepiliyana, 500); 53 | it('Kohuwala is a valid changeover', () => { 54 | expect(routes[0].changes).toContain(Kohuwala); 55 | }); 56 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BusRouteJS 2 | ========== 3 | 4 | A JS rewrite of bus route finder. 5 | 6 | Currently in preliminary development, so everything is subject to change 7 | without notice. 8 | 9 | Demo 10 | ---- 11 | 12 | [There's a demo here](http://janithl.github.io/BusRouteJS/). You 13 | have to turn on touch event emulation on Chrome to access the modal screens. 14 | 15 | Install 16 | ------- 17 | 18 | Clone the repo and run `npm install`. 19 | 20 | API 21 | --- 22 | 23 | Creating a new Router object and calling `.findRoutes(from, to)` where `from` is 24 | the source place ID and `to` is the destination place ID (both integers) will 25 | return an array of Javascript objects like this: 26 | 27 | ```javascript 28 | var router = new Router(); 29 | router.findRoutes(252, 235); 30 | 31 | [ 32 | { 33 | "from":"252", 34 | "routes":[ 35 | {"routes":["1","2","4","5","6","19","20"],"distance":4144}, 36 | {"routes":["26"],"distance":3347} 37 | ], 38 | "changes":["53"], 39 | "to":"235", 40 | "distance":7491 41 | }, 42 | { 43 | "from":"252", 44 | "routes":[ 45 | {"routes":["1","2","4","5","6","19","20"],"distance":12324}, 46 | {"routes":["12","13","14"],"distance":6661} 47 | ], 48 | "changes":["39"], 49 | "to":"235", 50 | "distance":18985 51 | }, 52 | { 53 | "from":"252", 54 | "routes":[ 55 | {"routes":["1","2","4","5","6","19","20"],"distance":12936}, 56 | {"routes":["12","13","14"],"distance":7273} 57 | ], 58 | "changes":["38"], 59 | "to":"235", 60 | "distance":20209 61 | }, 62 | ... 63 | ] 64 | ``` 65 | 66 | The biggest improvement over the last (PHP) version is that it now 67 | handles similar (almost duplicate, but alternate - like Kottawa/Pettah and Homagama/Pettah) 68 | routes gracefully, and runs completely on the clientside, with big popping 69 | Google Maps to boot. 70 | 71 | License 72 | ------- 73 | 74 | Released under a permissive MIT license. 75 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Bus Route Finder 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 42 | 43 |
44 | 45 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /js/userinput.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | 5 | class LocateMeButton extends Component { 6 | constructor() { 7 | super(); 8 | this.geolocate = this.geolocate.bind(this); 9 | } 10 | 11 | /** geolocate code from MDN: https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/Using_geolocation */ 12 | geolocate() { 13 | var _props = this.props; 14 | if (!navigator.geolocation) { 15 | _props.setError('Sorry! Your browser does not support Geolocation.'); 16 | return; 17 | } 18 | 19 | navigator.geolocation.getCurrentPosition(function(position) { 20 | _props.setUserLoc(position.coords.latitude, position.coords.longitude); /** on success */ 21 | }, function() { 22 | _props.setError('Sorry! There was an error in getting your location.'); /** on error */ 23 | }); 24 | } 25 | 26 | render() { 27 | return ( 28 | 31 | ); 32 | } 33 | } 34 | 35 | class Suggestion extends Component { 36 | constructor() { 37 | super(); 38 | this.setSuggestion = this.setSuggestion.bind(this); 39 | } 40 | 41 | setSuggestion() { 42 | this.props.setSuggestion(this.props.suggestion.id, this.props.suggestion.name); 43 | } 44 | 45 | render() { 46 | return {this.props.suggestion.name}; 47 | } 48 | } 49 | 50 | class SuggestionsList extends Component { 51 | render() { 52 | if(this.props.suggestions.length == 0) { 53 | return
54 | } 55 | else { 56 | var _props = this.props; 57 | return ( 58 |
59 | {this.props.suggestions.map(function(sug, index) { 60 | return ; 61 | })} 62 |
63 | ); 64 | } 65 | } 66 | } 67 | 68 | class SourceInput extends Component { 69 | constructor() { 70 | super(); 71 | this.handleInput = this.handleInput.bind(this); 72 | } 73 | 74 | handleInput(event) { 75 | this.props.setSource(null, event.target.value); 76 | } 77 | 78 | render() { 79 | return ( 80 |
81 | 83 | 84 | 85 | 86 |
87 | ); 88 | } 89 | } 90 | 91 | class DestinationInput extends Component { 92 | constructor() { 93 | super(); 94 | this.handleInput = this.handleInput.bind(this); 95 | } 96 | 97 | handleInput(event) { 98 | this.props.setDestination(null, event.target.value); 99 | } 100 | 101 | render() { 102 | return ( 103 |
104 | 106 | 107 | 110 | 111 |
112 | ); 113 | } 114 | } 115 | 116 | class UserInput extends Component { 117 | render() { 118 | return ( 119 |
120 |

Enter where you are and where you want to go below:

121 |
122 |
123 | 124 | 125 |
126 | 127 |

128 | 129 |
130 | 131 | 132 |
133 |
134 |
135 | ); 136 | } 137 | } 138 | 139 | export default UserInput; -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import Router from './router'; 7 | import UserInput from './userinput'; 8 | import DisplayRoutes from './displayroutes'; 9 | 10 | var router = new Router(); 11 | 12 | class DisplayAlert extends Component { 13 | render() { 14 | if(this.props.message) { 15 | var cssclass = 'alert alert-dismissible alert-' + this.props.cssclass; 16 | return ( 17 |
18 | 19 |

{this.props.title}

20 |

{this.props.message}

21 |
22 | ); 23 | } 24 | else { 25 | return
; 26 | } 27 | } 28 | } 29 | 30 | class App extends Component { 31 | constructor() { 32 | super(); 33 | this.state = { 34 | userLat: null, 35 | userLon: null, 36 | 37 | source: { id: null, name: '' }, 38 | sourceSug: [], 39 | 40 | destination: { id: null, name: '' }, 41 | destinationSug: [], 42 | 43 | locations: router.getAllPlaces(), 44 | routes: [], 45 | 46 | error: null, 47 | warning: null 48 | }; 49 | 50 | this.setUserLoc = this.setUserLoc.bind(this); 51 | this.setError = this.setError.bind(this); 52 | this.findRoutes = this.findRoutes.bind(this); 53 | 54 | this.setSource = this.setSource.bind(this); 55 | this.setDestination = this.setDestination.bind(this); 56 | this.readHash = this.readHash.bind(this); 57 | this.setHash = this.setHash.bind(this); 58 | 59 | this.filterSuggestions = this.filterSuggestions.bind(this); 60 | 61 | setTimeout(this.readHash, 500); 62 | } 63 | 64 | readHash() { 65 | if(window.location.hash.length > 1) { 66 | var hash = window.location.hash.substr(1).split('-'); 67 | if(hash.length == 2) { 68 | var from = router.getPlaceDetails(hash[0]); 69 | var to = router.getPlaceDetails(hash[1]); 70 | if(from && to) { 71 | this.setSource(hash[0], from.name); 72 | this.setDestination(hash[1], to.name); 73 | setTimeout(this.findRoutes, 500); 74 | } 75 | } 76 | } 77 | } 78 | 79 | setHash() { 80 | if(this.state.source.id && this.state.destination.id) { 81 | window.location.hash = '#' + this.state.source.id + '-' + this.state.destination.id; 82 | } 83 | } 84 | 85 | setError(error) { 86 | this.setState({error}); 87 | } 88 | 89 | setUserLoc(userLat, userLon) { 90 | this.setState({userLat, userLon}); 91 | 92 | var nearest = router.getNearestPlace(userLat, userLon); 93 | if(nearest) { 94 | this.setSource(nearest.id, nearest.name); 95 | 96 | if(nearest.distance > 1000) { 97 | this.setState({ 98 | warning: 'You might have to walk ' + (nearest.distance / 1000.0).toFixed(2) + 'km' 99 | }); 100 | } 101 | } 102 | } 103 | 104 | setSource(id, name) { 105 | this.setState({ 106 | source: { id: id, name: name }, 107 | sourceSug: id ? [] : this.filterSuggestions(name) 108 | }); 109 | if(id) { setTimeout(this.setHash, 500); } 110 | } 111 | 112 | setDestination(id, name) { 113 | this.setState({ 114 | destination: { id: id, name: name }, 115 | destinationSug: id ? [] : this.filterSuggestions(name) 116 | }); 117 | if(id) { setTimeout(this.setHash, 500); } 118 | } 119 | 120 | filterSuggestions(term) { 121 | if(term.length > 0) { 122 | var regexp = new RegExp(term, 'i'); 123 | return this.state.locations.filter(function(elem) { return regexp.test(elem.name); }).slice(0,4); 124 | } 125 | return []; 126 | } 127 | 128 | findRoutes() { 129 | this.setState({ routes: [] }); 130 | if(this.state.source.id && this.state.destination.id) { 131 | if(this.state.source.id === this.state.destination.id) { 132 | this.setError("Source and destination same"); 133 | } 134 | else { 135 | var routes = router.findRoutes(this.state.source.id, this.state.destination.id); 136 | if(routes && routes.length > 0) { 137 | this.setState({ routes: routes, error: null }); 138 | } 139 | else { 140 | this.setError("Sorry! No buses were found."); 141 | } 142 | } 143 | } 144 | else { 145 | this.setError("You haven't entered where you are and/or where you want to go!"); 146 | } 147 | } 148 | 149 | render() { 150 | return ( 151 |
152 | 159 | 160 | 161 | 162 | 163 | 164 |
165 | ); 166 | } 167 | } 168 | 169 | ReactDOM.render( 170 | , 171 | document.getElementById('app') 172 | ); 173 | 174 | -------------------------------------------------------------------------------- /js/displayroutes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React, { Component } from 'react'; 4 | 5 | class MapLink extends Component { 6 | constructor() { 7 | super(); 8 | this.renderMap = this.renderMap.bind(this); 9 | } 10 | 11 | renderMap() { 12 | $('#map-canvas').html(null); 13 | $('#map-title').html(this.props.link.name); 14 | 15 | setTimeout(function() { 16 | /** Google Map code stolen off http://stackoverflow.com/a/26410438 */ 17 | var centre = new google.maps.LatLng(this.props.link.lat, this.props.link.lon); 18 | var marker = new google.maps.Marker({ 19 | position: centre 20 | }); 21 | 22 | var mapProp = { 23 | center: centre, 24 | zoom: 16, 25 | mapTypeId: google.maps.MapTypeId.ROADMAP 26 | }; 27 | 28 | var map = new google.maps.Map(document.getElementById('map-canvas'), mapProp); 29 | marker.setMap(map); 30 | }.bind(this), 200); 31 | } 32 | 33 | render() { 34 | return( 35 | {this.props.link.name} 36 | ); 37 | } 38 | } 39 | 40 | class RenderRoute extends Component { 41 | render() { 42 | var from = this.props.router.getPlaceDetails(this.props.from); 43 | var to = this.props.router.getPlaceDetails(this.props.to); 44 | 45 | var details, busmarkup = this.props.route.routes.map(function(r, index) { 46 | details = this.props.router.getRouteDetails(r); 47 | if(details) { 48 | return #{details.routeno} ({details.from} - {details.to}) 49 | } 50 | }.bind(this)); 51 | 52 | if(from && to) { 53 | return ( 54 |
55 |
56 |

57 | to ({(this.props.route.distance / 1000.0).toFixed(2)} km) 58 |

59 |
60 |
{busmarkup}
61 |
62 | ); 63 | } 64 | else { 65 | return
; 66 | } 67 | } 68 | } 69 | 70 | class RenderOption extends Component { 71 | render() { 72 | var cssclass = this.props.first ? 'panel panel-warning' : 'panel panel-default'; 73 | 74 | var recommended =
; 75 | if(this.props.first) { 76 | recommended =
77 |

78 | Recommended Route 79 |

80 |
; 81 | } 82 | 83 | var components =
; 84 | if(this.props.route.changes.length == 0) { 85 | components =
86 |
87 | 92 |
93 |
; 94 | } 95 | else if(this.props.route.changes.length == 1) { 96 | components =
97 |
98 | 103 |
104 |
105 | 110 |
111 |
; 112 | } 113 | else if(this.props.route.changes.length == 2) { 114 | components =
115 |
116 | 121 |
122 |
123 | 128 |
129 |
130 | 135 |
136 |
; 137 | } 138 | 139 | return ( 140 |
141 | {recommended} 142 | {components} 143 |
144 | Total Distance: {(this.props.route.distance / 1000.0).toFixed(2)} km 145 |
146 |
147 | ); 148 | } 149 | } 150 | 151 | class DisplayRoutes extends Component { 152 | render() { 153 | var routes =
; 154 | if(this.props.routes && this.props.routes.length > 0) { 155 | routes = this.props.routes.map(function(r, index) { 156 | return 157 | }.bind(this)); 158 | } 159 | return ( 160 |
{routes}
161 | ); 162 | } 163 | } 164 | 165 | export default DisplayRoutes; -------------------------------------------------------------------------------- /js/router.js: -------------------------------------------------------------------------------- 1 | import Buses from './buses'; 2 | 3 | var Router = function () { 4 | this.buses = Buses; 5 | this.penalty = 5000; /** 5km penalty for changing buses */ 6 | }; 7 | 8 | /** stolen off http://stackoverflow.com/a/14438954 */ 9 | Router.prototype.unique = function(array) { 10 | return array.filter(function(item, pos) { 11 | return array.indexOf(item) == pos; 12 | }); 13 | }; 14 | 15 | /** stolen off http://stackoverflow.com/a/1885569 */ 16 | Router.prototype.intersect = function(array1, array2) { 17 | return this.unique(array1.filter(function(n) { 18 | return array2.indexOf(n) != -1; 19 | })); 20 | }; 21 | 22 | /** get distance from start node to end node on a route */ 23 | Router.prototype.getDistance = function(route, from, to) { 24 | if(!this.buses.routes.hasOwnProperty(route)) { 25 | return 1/0; 26 | } 27 | 28 | var fromdist = this.buses.routes[route].stopsfrom[to] - this.buses.routes[route].stopsfrom[from]; 29 | var todist = this.buses.routes[route].stopsto[to] - this.buses.routes[route].stopsto[from]; 30 | 31 | return fromdist > 0 ? fromdist : (todist > 0 ? todist : 1/0); 32 | }; 33 | 34 | /** 35 | see if an end node is reachable from a start node on a given route. 36 | distance of the end node should be greater than the start node 37 | */ 38 | Router.prototype.reachable = function(route, from, to) { 39 | return this.buses.routes.hasOwnProperty(route) && 40 | (this.buses.routes[route].stopsfrom[to] > this.buses.routes[route].stopsfrom[from] || 41 | this.buses.routes[route].stopsto[to] > this.buses.routes[route].stopsto[from]); 42 | }; 43 | 44 | 45 | /** find stops that can be reached from a given stop on a given route */ 46 | Router.prototype.findReachableStops = function(route, stop) { 47 | var stops = []; 48 | if(this.buses.routes.hasOwnProperty(route) && 49 | this.buses.routes[route].stopsfrom.hasOwnProperty(stop)) { 50 | var startdist = this.buses.routes[route].stopsfrom[stop]; 51 | for(var s in this.buses.routes[route].stopsfrom) { 52 | if(this.buses.routes[route].stopsfrom[s] > startdist) { 53 | stops.push(s); 54 | } 55 | } 56 | } 57 | 58 | if(this.buses.routes.hasOwnProperty(route) && 59 | this.buses.routes[route].stopsto.hasOwnProperty(stop)) { 60 | var startdist = this.buses.routes[route].stopsto[stop]; 61 | for(var s in this.buses.routes[route].stopsto) { 62 | if(this.buses.routes[route].stopsto[s] > startdist) { 63 | stops.push(s); 64 | } 65 | } 66 | } 67 | 68 | return this.unique(stops); 69 | }; 70 | 71 | /** find routes that pass through the given stop */ 72 | Router.prototype.findStopRoutes = function(stop) { 73 | var routes = []; 74 | for(var r in this.buses.routes) { 75 | if(this.buses.routes[r].stopsfrom.hasOwnProperty(stop) || 76 | this.buses.routes[r].stopsto.hasOwnProperty(stop)) { 77 | routes.push(r); 78 | } 79 | } 80 | 81 | return this.unique(routes); 82 | }; 83 | 84 | /** find single bus routes between two nodes */ 85 | Router.prototype.findSingleRoutes = function(from, to) { 86 | var _self = this; 87 | 88 | var fromRoutes = this.findStopRoutes(from); /** bus routes passing through start node */ 89 | var toRoutes = this.findStopRoutes(to); /** bus routes passing through end node */ 90 | 91 | return this.intersect(fromRoutes, toRoutes).filter(function(r) { return _self.reachable(r, from, to); }); 92 | }; 93 | 94 | /** find routes between two nodes */ 95 | Router.prototype.findRoutes = function(from, to, limit = 5) { 96 | var fromRoutes = this.findStopRoutes(from); /** bus routes passing through start node */ 97 | var toRoutes = this.findStopRoutes(to); /** bus routes passing through end node */ 98 | 99 | /** try to find a single route from start node to end node, and return */ 100 | var singleRoute = this.findSingleRoutes(from, to); 101 | 102 | if(singleRoute.length > 0) { 103 | singleRoute = singleRoute.sort(function(a, b) { 104 | return a.distance - b.distance; 105 | }); 106 | 107 | var distance = this.getDistance(singleRoute[0], from, to); 108 | return [{ 109 | from : from, 110 | routes : [ 111 | { routes: singleRoute.slice(0, 5), distance: distance } 112 | ], 113 | changes : [], 114 | to : to, 115 | distance: distance 116 | }]; 117 | } 118 | else { 119 | /** 120 | if not found, try to find reachable nodes from the first route, and 121 | from the second route, and any intersecting stops 122 | */ 123 | var fromStops = [], toStops = [], distances, multiRoutes = [], _self = this; 124 | fromRoutes.forEach(function(fr) { 125 | fromStops = fromStops.concat(_self.findReachableStops(fr, from)); 126 | }); 127 | 128 | toRoutes.forEach(function(tr) { 129 | toStops = toStops.concat(_self.findReachableStops(tr, to)); 130 | }); 131 | 132 | /** 133 | 2 bus: find intersecting stops, and routes to take you from your starting node 134 | to your intersection, and from the intersection to the end stop 135 | */ 136 | var common = this.intersect(fromStops, toStops); 137 | common.forEach(function(c) { 138 | var toc = _self.findSingleRoutes(from, c); 139 | var fromc = _self.findSingleRoutes(c, to); 140 | 141 | if(toc.length > 0 && fromc.length > 0) { 142 | distances = [ 143 | _self.getDistance(toc[0], from, c), 144 | _self.getDistance(fromc[0], c, to) 145 | ]; 146 | 147 | multiRoutes.push({ 148 | from : from, 149 | routes : [ 150 | { routes: toc, distance: distances[0] }, 151 | { routes: fromc, distance: distances[1] } 152 | ], 153 | changes : [c], 154 | to : to, 155 | distance: distances[0] + distances[1] 156 | }); 157 | } 158 | }); 159 | 160 | if(common.length < 3) { 161 | /** 162 | 3 bus: find reachable stops from start and end nodes, and find 163 | routes connecting some nodes within those two sets 164 | */ 165 | fromStops.forEach(function(fs) { 166 | toStops.forEach(function(ts) { 167 | var tots = _self.findSingleRoutes(fs, ts); 168 | if(tots.length > 0) { 169 | var tofs = _self.findSingleRoutes(from, fs); 170 | var fromts = _self.findSingleRoutes(ts, to); 171 | 172 | if(tofs.length > 0 && fromts.length > 0) { 173 | distances = [ 174 | _self.getDistance(tofs[0], from, fs), 175 | _self.getDistance(tots[0], fs, ts), 176 | _self.getDistance(fromts[0], ts, to) 177 | ]; 178 | 179 | multiRoutes.push({ 180 | from : from, 181 | routes : [ 182 | { routes: tofs, distance: distances[0] }, 183 | { routes: tots, distance: distances[1] }, 184 | { routes: fromts, distance: distances[2] } 185 | ], 186 | changes : [fs, ts], 187 | to : to, 188 | distance: distances[0] + distances[1] + distances[2] 189 | }); 190 | } 191 | } 192 | }); 193 | }); 194 | } 195 | 196 | if(multiRoutes.length > 0) { 197 | multiRoutes = multiRoutes.filter(function(n) { 198 | return !Number.isNaN(n.distance); 199 | }).sort(function(a, b) { 200 | return (a.routes.length - b.routes.length) * _self.penalty + (a.distance - b.distance); 201 | }); 202 | return multiRoutes.slice(0, limit); 203 | } 204 | } 205 | }; 206 | 207 | Router.prototype.getPlaceDetails = function(pid) { 208 | return this.buses.places[pid]; 209 | }; 210 | 211 | Router.prototype.getRouteDetails = function(id) { 212 | return this.buses.routes[id]; 213 | }; 214 | 215 | /** get a list of all places, for autosuggest */ 216 | Router.prototype.getAllPlaces = function() { 217 | var places = []; 218 | for(var p in this.buses.places) { 219 | places.push({ id: p, name: this.buses.places[p].name }); 220 | } 221 | return places; 222 | }; 223 | 224 | /** Haversine formula, stolen off http://stackoverflow.com/a/27943 */ 225 | Router.prototype.deg2rad = function(deg) { 226 | return deg * (Math.PI/180) 227 | } 228 | 229 | Router.prototype.haversine = function(lat1, lon1, lat2, lon2) { 230 | var R = 6371; // Radius of the earth in km 231 | var dLat = this.deg2rad(lat2 - lat1); // deg2rad below 232 | var dLon = this.deg2rad(lon2 - lon1); 233 | var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 234 | Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) * 235 | Math.sin(dLon/2) * Math.sin(dLon/2); 236 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 237 | return Math.floor(R * c * 1000); // Distance in metres 238 | } 239 | 240 | /** get the stop nearest to a geolocation */ 241 | Router.prototype.getNearestPlace = function(lat, lon) { 242 | var nearby = [], place; 243 | for(var p in this.buses.places) { 244 | if(Math.abs(this.buses.places[p].lat - lat) < 0.1 && 245 | Math.abs(this.buses.places[p].lon - lon) < 0.1) { 246 | place = this.buses.places[p]; 247 | place.id = p; 248 | place.distance = this.haversine(place.lat, place.lon, lat, lon); 249 | nearby.push(place); 250 | } 251 | } 252 | return nearby.sort(function(a, b) { 253 | return b.distance - a.distance; 254 | }).pop(); 255 | }; 256 | 257 | export default Router; -------------------------------------------------------------------------------- /bundle.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o1){var hash=window.location.hash.substr(1).split("-");if(hash.length==2){var from=router.getPlaceDetails(hash[0]);var to=router.getPlaceDetails(hash[1]);if(from&&to){this.setSource(hash[0],from.name);this.setDestination(hash[1],to.name);setTimeout(this.findRoutes,500)}}}}},{key:"setHash",value:function setHash(){if(this.state.source.id&&this.state.destination.id){window.location.hash="#"+this.state.source.id+"-"+this.state.destination.id}}},{key:"setError",value:function setError(error){this.setState({error:error})}},{key:"setUserLoc",value:function setUserLoc(userLat,userLon){this.setState({userLat:userLat,userLon:userLon});var nearest=router.getNearestPlace(userLat,userLon);if(nearest){this.setSource(nearest.id,nearest.name);if(nearest.distance>1e3){this.setState({warning:"You might have to walk "+(nearest.distance/1e3).toFixed(2)+"km"})}}}},{key:"setSource",value:function setSource(id,name){this.setState({source:{id:id,name:name},sourceSug:id?[]:this.filterSuggestions(name)});if(id){setTimeout(this.setHash,500)}}},{key:"setDestination",value:function setDestination(id,name){this.setState({destination:{id:id,name:name},destinationSug:id?[]:this.filterSuggestions(name)});if(id){setTimeout(this.setHash,500)}}},{key:"filterSuggestions",value:function filterSuggestions(term){if(term.length>0){var regexp=new RegExp(term,"i");return this.state.locations.filter(function(elem){return regexp.test(elem.name)}).slice(0,4)}return[]}},{key:"findRoutes",value:function findRoutes(){this.setState({routes:[]});if(this.state.source.id&&this.state.destination.id){if(this.state.source.id===this.state.destination.id){this.setError("Source and destination same")}else{var routes=router.findRoutes(this.state.source.id,this.state.destination.id);if(routes&&routes.length>0){this.setState({routes:routes,error:null})}else{this.setError("Sorry! No buses were found.")}}}else{this.setError("You haven't entered where you are and/or where you want to go!")}}},{key:"render",value:function render(){return _react2.default.createElement("div",null,_react2.default.createElement(_userinput2.default,{appState:this.state,setUserLoc:this.setUserLoc,setError:this.setError,setSource:this.setSource,setDestination:this.setDestination,findRoutes:this.findRoutes}),_react2.default.createElement(DisplayAlert,{cssclass:"danger",title:"Error!",message:this.state.error}),_react2.default.createElement(DisplayAlert,{cssclass:"warning",title:"Warning!",message:this.state.warning}),_react2.default.createElement(_displayroutes2.default,{routes:this.state.routes,router:router}))}}]);return App}(_react.Component);_reactDom2.default.render(_react2.default.createElement(App,null),document.getElementById("app"))}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{"./displayroutes":3,"./router":4,"./userinput":5}],2:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var Buses={places:{1:{name:"Pettah",lat:6.93321,lon:79.8554},2:{name:"Fort Railway Station",lat:6.93408,lon:79.8502},3:{name:"Lotus Road",lat:6.93398,lon:79.847},4:{name:"Galle Face Green",lat:6.92196,lon:79.8461},5:{name:"Kollupitiya Junction",lat:6.91116,lon:79.8497},6:{name:"McDonalds, Kollupitiya",lat:6.90317,lon:79.8523},7:{name:"Bambalapitiya Junction",lat:6.89369,lon:79.8555},8:{name:"Holy Family Convent, Bambalapitiya",lat:6.88781,lon:79.8573},9:{name:"Savoy Cinema, Wellawatte",lat:6.88027,lon:79.8596},10:{name:"Wellawatte Junction",lat:6.87534,lon:79.861},16:{name:"Lake House",lat:6.93261,lon:79.848},19:{name:"Gamini Hall Junction",lat:6.92653,lon:79.8618},20:{name:"Darley Road/Excel World",lat:6.92166,lon:79.8622},21:{name:"Gangarama",lat:6.91811,lon:79.8543},22:{name:"Bishop's College",lat:6.91459,lon:79.8559},23:{name:"Mahanama College",lat:6.90568,lon:79.8536},24:{name:"British Council",lat:6.89983,lon:79.8555},25:{name:"Regal Cinema",lat:6.93128,lon:79.847},26:{name:"Slave Island",lat:6.9233,lon:79.8516},27:{name:"Town Hall",lat:6.9168,lon:79.8634},28:{name:"Public Library",lat:6.91287,lon:79.8579},29:{name:"St. Bridget's Convent",lat:6.90965,lon:79.8637},30:{name:"Race Course Grounds",lat:6.90618,lon:79.8637},31:{name:"Campus (Arts Faculty)",lat:6.90258,lon:79.8622},32:{name:"Campus (near Wycherley)",lat:6.90002,lon:79.8595},33:{name:"Thurstan College",lat:6.90502,lon:79.8583},34:{name:"Glass House",lat:6.91094,lon:79.8583},35:{name:"Thunmulla",lat:6.89619,lon:79.8603},36:{name:"Police Park",lat:6.89288,lon:79.862},37:{name:"Thimbirigasyaya",lat:6.88998,lon:79.8638},38:{name:"BRC Grounds",lat:6.88753,lon:79.8645},39:{name:"Redimola Junction",lat:6.88363,lon:79.8683},40:{name:"Maya Ave.",lat:6.88077,lon:79.8704},41:{name:"Kirulapone Junction",lat:6.87858,lon:79.8746},42:{name:"Kirulapone South",lat:6.87772,lon:79.8786},43:{name:"Balapokuna Road",lat:6.87384,lon:79.8819},44:{name:"Anula Vidyalaya",lat:6.87195,lon:79.8843},45:{name:"Nugegoda",lat:6.86932,lon:79.8896},46:{name:"Seventh Mile Post",lat:6.86686,lon:79.8933},47:{name:"Gansabha Junction",lat:6.86511,lon:79.8966},48:{name:"Delkanda",lat:6.86282,lon:79.9018},49:{name:"Wijerama",lat:6.85764,lon:79.9086},50:{name:"Navinna",lat:6.8551,lon:79.9134},51:{name:"Arpico M'gama",lat:6.85243,lon:79.917},52:{name:"Wattegedara Junction",lat:6.85123,lon:79.9211},53:{name:"Maharagama",lat:6.84608,lon:79.9281},74:{name:"Sugathadasa Stadium",lat:6.94666,lon:79.8691},75:{name:"Armour Street",lat:6.94343,lon:79.8643},76:{name:"Ananda College",lat:6.9245,lon:79.868},77:{name:"Maradana",lat:6.92824,lon:79.8646},78:{name:"Castle Street Hospital",lat:6.91097,lon:79.8852},79:{name:"Devi Balika Vidyalaya",lat:6.91102,lon:79.8821},80:{name:"Alwis Place, Kollupitiya",lat:6.91264,lon:79.8539},81:{name:"Arts Fac. Horton Place",lat:6.91155,lon:79.8687},82:{name:"Green Path",lat:6.91173,lon:79.862},83:{name:"Stratford Avenue",lat:6.87761,lon:79.873},84:{name:"Kirulapone Ave.",lat:6.88269,lon:79.8756},85:{name:"Suwisuddharamaya",lat:6.88013,lon:79.8696},86:{name:"Sapphire Halt",lat:6.8767,lon:79.8664},87:{name:"Pamankada",lat:6.87698,lon:79.8697},88:{name:"Vijaya Kumaratunge Mw.",lat:6.88553,lon:79.8767},89:{name:"Apollo Hospital",lat:6.88922,lon:79.8769},90:{name:"Narahenpita Junction",lat:6.89189,lon:79.877},94:{name:"Ibbanwela Junction",lat:6.91879,lon:79.8613},95:{name:"Ward Place",lat:6.91721,lon:79.8662},100:{name:"Borella",lat:6.91474,lon:79.8776},101:{name:"Horton Place - Baseline Junction",lat:6.91126,lon:79.8775},102:{name:"Borella Cemetery Junction",lat:6.90859,lon:79.8773},103:{name:"Sarana Road",lat:6.90558,lon:79.8735},104:{name:"Maitland Place",lat:6.9029,lon:79.8705},201:{name:"Delmon Hospital",lat:6.87071,lon:79.8621},202:{name:"Ramakrishna Road",lat:6.8659,lon:79.863},203:{name:"William Grinding Mills",lat:6.86251,lon:79.8638},204:{name:"Dehiwala Municipal Council",lat:6.86103,lon:79.8641},205:{name:"St. Mary's Church, Dehiwala",lat:6.85918,lon:79.8645},206:{name:"Holy Family Convent, Dehiwala",lat:6.85717,lon:79.865},207:{name:"Dehiwala Junction",lat:6.85082,lon:79.866},208:{name:"Dehiwala Cemetery",lat:6.84493,lon:79.8662},209:{name:"Hotel Road, Mt. Lavinia",lat:6.84197,lon:79.8669},210:{name:"S. Thomas' College, Mt. Lavinia",lat:6.83775,lon:79.8674},211:{name:"Mount Lavinia Junction",lat:6.83296,lon:79.8673},212:{name:"Maliban Junction",lat:6.81943,lon:79.8737},213:{name:"Belekkade Junc., Rathmalana",lat:6.81474,lon:79.8788},214:{name:"Rathmalana Airport",lat:6.81244,lon:79.8811},215:{name:"Soysapura",lat:6.80462,lon:79.8867},216:{name:"Katubedda Junction",lat:6.79733,lon:79.8885},217:{name:"Rawatawatta",lat:6.78803,lon:79.8851},218:{name:"Moratuwa",lat:6.77449,lon:79.8824},219:{name:"Panadura",lat:6.71138,lon:79.9076},220:{name:"Angulana",lat:6.79819,lon:79.873},230:{name:"Dutugemunu Street",lat:6.87059,lon:79.8774},231:{name:"Kohuwala",lat:6.86707,lon:79.8846},232:{name:"Woodlands",lat:6.8623,lon:79.8884},233:{name:"Pepiliyana",lat:6.85666,lon:79.8906},234:{name:"Raththanapitiya",lat:6.84834,lon:79.8976},235:{name:"Boralesgamuwa",lat:6.84124,lon:79.9016},236:{name:"Werahera",lat:6.82972,lon:79.9114},237:{name:"Bokundara",lat:6.81862,lon:79.9177},238:{name:"Piliyandala",lat:6.80122,lon:79.9233},239:{name:"Kesbewa",lat:6.79552,lon:79.9408},240:{name:"Polgasovita",lat:6.78698,lon:79.9649},241:{name:"Kahathuduwa",lat:6.78346,lon:79.9834},242:{name:"Horana",lat:6.71662,lon:80.0638},244:{name:"Panagoda",lat:6.84827,lon:80.0188},245:{name:"Godagama",lat:6.85148,lon:80.0316},246:{name:"Migoda",lat:6.84423,lon:80.0467},247:{name:"Padukka",lat:6.84146,lon:80.0908},248:{name:"Handapangoda",lat:6.79234,lon:80.1426},249:{name:"Ingiriya",lat:6.74399,lon:80.1766},250:{name:"Teachers' Training College",lat:6.84471,lon:79.9327},251:{name:"Pannipitiya",lat:6.8462,lon:79.9497},252:{name:"Kottawa",lat:6.84167,lon:79.964},253:{name:"Makumbura",lat:6.83862,lon:79.98},254:{name:"Homagama",lat:6.84113,lon:80.002},255:{name:"Kottawa Railway Station",lat:6.84401,lon:79.9682},256:{name:"Rukmalgama",lat:6.85692,lon:79.9859},257:{name:"Walgama Junc., Athurugiriya",lat:6.86495,lon:79.9958},258:{name:"Athurugiriya Junction",lat:6.87745,lon:79.9895},259:{name:"Pinhena Junction",lat:6.83278,lon:79.9647},260:{name:"Mattegoda",lat:6.81158,lon:79.9751},271:{name:"Open University, Nawala",lat:6.88302,lon:79.8868},272:{name:"Nawala Junction",lat:6.88703,lon:79.8872},273:{name:"Koswatta, Nawala",lat:6.90003,lon:79.894},274:{name:"Bellanthota Junction",lat:6.84831,lon:79.8854},275:{name:"Nandimala",lat:6.84986,lon:79.8794},276:{name:"Karagampitiya",lat:6.85025,lon:79.872},277:{name:"Kalubowila Hospital",lat:6.86647,lon:79.8773},278:{name:"Nugegoda Supermarket",lat:6.87347,lon:79.8914},279:{name:"Pita Kotte",lat:6.8839,lon:79.9019},280:{name:"Impala Cinema, Rajagiriya",lat:6.91097,lon:79.8907},281:{name:"Rajagiriya Junction",lat:6.91012,lon:79.8944},282:{name:"Ethul Kotte Junction",lat:6.90551,lon:79.9052},283:{name:"Sethsiripaya",lat:6.90244,lon:79.9143},284:{name:"Battaramulla",lat:6.90209,lon:79.9181},285:{name:"Thalangama",lat:6.90597,lon:79.9262},286:{name:"Thalahena",lat:6.90801,lon:79.945},287:{name:"Malabe",lat:6.90408,lon:79.9544},288:{name:"Pittugala",lat:6.90869,lon:79.9693},289:{name:"SLIIT, Malabe",lat:6.91471,lon:79.9722},290:{name:"Kaduwela",lat:6.9357,lon:79.9842},291:{name:"Thalawathugoda",lat:6.87628,lon:79.9353},292:{name:"Pelawatta/Isurupaya",lat:6.8906,lon:79.9289},295:{name:"Peliyagoda Junction",lat:6.95576,lon:79.8831},296:{name:"Nawaloka Junction",lat:6.96068,lon:79.8808},297:{name:"Wattala",lat:6.98125,lon:79.888},298:{name:"Handala",lat:6.99069,lon:79.8931},299:{name:"Welisara",lat:7.02178,lon:79.8996},300:{name:"Kandana",lat:7.04783,lon:79.897},301:{name:"Kapuwatta",lat:7.06401,lon:79.8932},302:{name:"Ja-Ela",lat:7.07824,lon:79.8905},303:{name:"Katunayake Airport",lat:7.16564,lon:79.8841}},routes:{1:{routeno:"138",from:"Pettah",to:"Kottawa",stopsfrom:{1:0,2:609,25:2503,26:4056,27:6664,28:8217,29:9130,30:9570,31:10021,34:8440,35:10805,36:11226,37:11614,38:11898,39:12510,40:12907,41:13517,42:13969,43:14564,44:14911,45:15567,46:16073,47:16517,48:17149,49:18114,50:18724,51:19238,52:19717,53:21116,250:22627,251:24548,252:26231},stopsto:{1:22556,2:21962,25:20875,26:19802,27:18232,28:16762,32:14544,33:15110,34:16002,35:14029,36:13608,37:13220,38:12936,39:12324,40:11918,41:11308,42:10856,43:10261,44:9914,45:9258,46:8752,47:8308,48:7676,49:6711,50:6101,51:5587,52:5108,53:4144,250:3604,251:1683,252:0}},2:{routeno:"138",from:"Pettah",to:"Homagama",stopsfrom:{1:0,2:609,25:2503,26:4056,27:6664,28:8217,29:9130,30:9570,31:10021,34:8440,35:10805,36:11226,37:11614,38:11898,39:12510,40:12907,41:13517,42:13969,43:14564,44:14911,45:15567,46:16073,47:16517,48:17149,49:18114,50:18724,51:19238,52:19717,53:21116,250:22627,251:24548,252:26231,253:28080,254:30597},stopsto:{1:26922,2:26328,25:25241,26:24168,27:22598,28:21128,32:18910,33:19476,34:20368,35:18395,36:17974,37:17586,38:17302,39:16690,40:16284,41:15674,42:15222,43:14627,44:14280,45:13624,46:13118,47:12674,48:12042,49:11077,50:10467,51:9953,52:9474,53:8510,250:7970,251:6049,252:4366,253:2517,254:0}},3:{routeno:"138",from:"Pettah",to:"Maharagama",stopsfrom:{1:0,2:609,25:2503,26:4056,27:6664,28:8217,29:9130,30:9570,31:10021,34:8440,35:10805,36:11226,37:11614,38:11898,39:12510,40:12907,41:13517,42:13969,43:14564,44:14911,45:15567,46:16073,47:16517,48:17149,49:18114,50:18724,51:19238,52:19717,53:21116},stopsto:{1:18412,2:17818,25:16731,26:15658,27:14088,28:12618,32:10400,33:10966,34:11858,35:9885,36:9464,37:9076,38:8792,39:8180,40:7774,41:7164,42:6712,43:6117,44:5770,45:5114,46:4608,47:4164,48:3532,49:2567,50:1957,51:1443,52:964,53:0}},4:{routeno:"138/2",from:"Pettah",to:"Mattegoda",stopsfrom:{1:0,2:609,25:2503,26:4056,27:6664,28:8217,29:9130,30:9570,31:10021,34:8440,35:10805,36:11226,37:11614,38:11898,39:12510,40:12907,41:13517,42:13969,43:14564,44:14911,45:15567,46:16073,47:16517,48:17149,49:18114,50:18724,51:19238,52:19717,53:21116,250:22627,251:24548,252:26231,259:27340,260:30130},stopsto:{1:26455,2:25861,25:24774,26:23701,27:22131,28:20661,32:18443,33:19009,34:19901,35:17928,36:17507,37:17119,38:16835,39:16223,40:15817,41:15207,42:14755,43:14160,44:13813,45:13157,46:12651,47:12207,48:11575,49:10610,50:1e4,51:9486,52:9007,53:8043,250:7503,251:5582,252:3899,259:2790,260:0}},5:{routeno:"138/4",from:"Pettah",to:"Athurugiriya",stopsfrom:{1:0,2:609,25:2503,26:4056,27:6664,28:9086,29:9999,30:10439,31:10890,34:8326,35:11674,36:12095,37:12483,38:12767,39:13379,40:13776,41:14386,42:14838,43:15433,44:15780,45:16436,46:16942,47:17386,48:18018,49:18983,50:19593,51:20107,52:20586,53:21985,250:23496,251:25417,252:27100,255:27650,256:30454,257:31999,258:33750},stopsto:{1:29206,2:28612,25:27525,26:26452,27:24882,28:23412,32:21194,33:21760,34:22652,35:20679,36:20258,37:19870,38:19586,39:18974,40:18568,41:17958,42:17506,43:16911,44:16564,45:15908,46:15402,47:14958,48:14326,49:13361,50:12751,51:12237,52:11758,53:10794,250:10254,251:8333,252:6650,255:6100,256:3296,257:1751,258:0}},6:{routeno:"138/3",from:"Pettah",to:"Rukmalgama",stopsfrom:{1:0,2:609,25:2503,26:4056,27:6664,28:8217,29:9130,30:9570,31:10021,34:8440,35:10805,36:11226,37:11614,38:11898,39:12510,40:12907,41:13517,42:13969,43:14564,44:14911,45:15567,46:16073,47:16517,48:17149,49:18114,50:18724,51:19238,52:19717,53:21116,250:22627,251:24548,252:26231,255:26781,256:29585},stopsto:{1:25910,2:25316,25:24229,26:23156,27:21586,28:20116,32:17898,33:18464,34:19356,35:17383,36:16962,37:16574,38:16290,39:15678,40:15272,41:14662,42:14210,43:13615,44:13268,45:12612,46:12106,47:11662,48:11030,49:10065,50:9455,51:8941,52:8462,53:7498,250:6958,251:5037,252:3354,255:2804,256:0}},7:{routeno:"100",from:"Pettah",to:"Panadura",stopsfrom:{1:0,2:609,3:964,4:3048,5:5304,7:7872,8:9380,9:10656,10:11225,23:6312,24:6995,201:11755,202:12299,203:12686,204:12855,205:13065,206:13296,207:14012,208:14668,209:15007,210:15486,211:16019,212:17703,213:18479,214:18838,215:19914,216:20767,217:21871,218:23505,219:31585},stopsto:{1:29615,2:29021,3:28666,4:26417,5:25135,6:24202,7:22646,8:21737,9:20860,10:20291,201:19761,202:19217,203:18830,204:18661,205:18451,206:18220,207:17504,208:16848,209:16509,210:16030,211:15497,212:13813,213:13037,214:12678,215:11602,216:10749,217:9645,218:8060,219:0}},8:{routeno:"100",from:"Pettah",to:"Moratuwa",stopsfrom:{1:0,2:609,3:964,4:3048,5:5304,7:7872,8:9380,9:10656,10:11225,23:6312,24:6995,201:11755,202:12299,203:12686,204:12855,205:13065,206:13296,207:14012,208:14668,209:15007,210:15486,211:16019,212:17703,213:18479,214:18838,215:19914,216:20767,217:21871,218:23505},stopsto:{1:21555,2:20961,3:20606,4:18357,5:17075,6:16142,7:14586,8:13677,9:12800,10:12231,201:11701,202:11157,203:10770,204:10601,205:10391,206:10160,207:9444,208:8788,209:8449,210:7970,211:7437,212:5753,213:4977,214:4618,215:3542,216:2689,217:1585,218:0}},9:{routeno:"101",from:"Pettah",to:"Moratuwa",stopsfrom:{1:0,2:609,5:7694,7:10262,8:11770,9:13046,10:13615,21:5065,22:5513,23:8702,24:9385,25:2503,26:4056,201:14145,202:14689,203:15076,204:15245,205:15455,206:15686,207:16402,208:17058,209:17397,210:17876,211:18409,212:20093,213:20869,214:21228,215:22304,216:23157,217:24261,218:25895},stopsto:{1:21882,2:21288,5:17075,6:16142,7:14586,8:13677,9:12800,10:12231,21:18456,22:18008,25:20201,26:19128,201:11701,202:11157,203:10770,204:10601,205:10391,206:10160,207:9444,208:8788,209:8449,210:7970,211:7437,212:5753,213:4977,214:4618,215:3542,216:2689,217:1585,218:0}},10:{routeno:"154",from:"Kiribathgoda",to:"Angulana",stopsfrom:{7:4627,8:6135,9:7411,10:7980,31:2970,35:3754,100:0,101:386,102:683,103:1242,104:1688,201:8510,202:9054,203:9441,204:9610,205:9820,206:10051,207:10767,208:11423,209:11762,210:12241,211:12774,212:14458,213:15234,214:15593,220:18383},stopsto:{7:0,8:25647,9:26923,10:27492,24:30770,30:33289,32:34432,33:34998,35:36629,100:39880,101:40266,102:40563,103:41122,104:22437,201:17276,202:16732,203:4042,204:4211,205:4421,206:4652,207:5368,208:6024,209:6363,210:6842,211:7375,212:9059,213:9835,214:10194,220:56255}},11:{routeno:"154",from:"Kadawatha",to:"Bambalapitiya",stopsfrom:{7:4627,31:2970,35:3754,100:0,101:386,102:683,103:1242,104:1688},stopsto:{7:0,24:14461,30:12325,32:10401,33:8861,35:7780,100:4624,101:4238,102:3830,103:3231,104:16677}},12:{routeno:"120",from:"Pettah",to:"Horana",stopsfrom:{1:0,2:609,16:1385,19:3208,20:3752,27:5070,29:6191,30:6631,31:7082,35:7866,36:8287,37:8675,38:8959,39:9571,40:9968,83:10480,230:11622,231:12597,232:13307,233:14020,234:15296,235:16232,236:17997,237:19432,238:21554,239:23759,240:26657,241:28879,242:42291},stopsto:{1:43822,2:43228,16:42069,19:40317,20:39773,27:39056,29:36278,32:34800,33:35366,35:34285,36:33864,37:33476,38:33192,39:32580,85:32138,87:31776,230:30597,231:29677,232:28983,233:28270,234:26994,235:26058,236:24294,237:22859,238:20737,239:18532,240:15634,241:13412,242:0}},13:{routeno:"120",from:"Pettah",to:"Kesbewa",stopsfrom:{1:0,2:609,16:1385,19:3208,20:3752,27:5070,29:6191,30:6631,31:7082,35:7866,36:8287,37:8675,38:8959,39:9571,40:9968,83:10480,230:11622,231:12597,232:13307,233:14020,234:15296,235:16232,236:17997,237:19432,238:21554,239:23759},stopsto:{1:25290,2:24696,16:23537,19:21785,20:21241,27:20524,29:17746,32:16268,33:16834,35:15753,36:15332,37:14944,38:14660,39:14048,85:13606,87:13244,230:12065,231:11145,232:10451,233:9738,234:8462,235:7526,236:5762,237:4327,238:2205,239:0}},14:{routeno:"120",from:"Pettah",to:"Piliyandala",stopsfrom:{1:0,2:609,16:1385,19:3208,20:3752,27:5070,29:6191,30:6631,31:7082,35:7866,36:8287,37:8675,38:8959,39:9571,40:9968,83:10480,230:11622,231:12597,232:13307,233:14020,234:15296,235:16232,236:17997,237:19432,238:21554},stopsto:{1:23085,2:22491,16:21332,19:19580,20:19036,27:18319,29:15541,32:14063,33:14629,35:13548,36:13127,37:12739,38:12455,39:11843,85:11401,87:11039,230:9860,231:8940,232:8246,233:7533,234:6257,235:5321,236:3557,237:2122,238:0}},15:{routeno:"141",from:"Narahenpita",to:"Wellawatte",stopsfrom:{90:0,89:297,88:721,41:1755,87:3098,86:3489,10:4121},stopsto:{10:0,86:804,87:1169,85:1451,40:1685,41:2219,84:2809,88:3212,89:3620,90:3864}},16:{routeno:"135",from:"Kohuwala",to:"Kelaniya",stopsfrom:{37:3799,38:3515,39:2903,85:2461,87:2099,90:6019,100:9098,101:8712,102:8304,230:920,231:0},stopsto:{37:0,38:17396,39:16784,40:16378,83:14646,90:12574,100:10032,101:9646,102:9238,230:2947,231:21467}},17:{routeno:"122",from:"Pettah",to:"Avissawella",stopsfrom:{},stopsto:{}},18:{routeno:"122",from:"Pettah",to:"Rathnapura",stopsfrom:{},stopsto:{}},19:{routeno:"125",from:"Pettah",to:"Padukka",stopsfrom:{1:0,2:609,16:1385,19:3208,20:3752,27:5070,28:6623,29:7536,30:7976,31:8427,35:9211,36:9632,37:10020,38:10304,39:10916,40:11313,41:11923,42:12375,43:12970,44:13317,45:13973,46:14479,47:14923,48:15555,49:16520,50:17130,51:17644,52:18123,53:19522,244:31478,245:37986,246:39907,247:45709,250:21033,251:22954,252:24637,253:26486,254:33954},stopsto:{1:39687,2:39093,16:37934,19:36182,20:35638,27:34921,29:32143,32:30665,33:31231,35:30150,36:29729,37:29341,38:29057,39:28445,40:28039,41:27429,42:26977,43:26382,44:26035,45:25379,46:24873,47:24429,48:23797,49:22832,50:22222,51:21708,52:21229,53:20265,244:9279,245:7723,246:5802,247:0,250:19725,251:17804,252:16121,254:11755}},20:{routeno:"125",from:"Pettah",to:"Ingiriya",stopsfrom:{1:0,2:609,16:1385,19:3208,20:3752,27:5070,28:6623,29:7536,30:7976,31:8427,35:9211,36:9632,37:10020,38:10304,39:10916,40:11313,41:11923,42:12375,43:12970,44:13317,45:13973,46:14479,47:14923,48:15555,49:16520,50:17130,51:17644,52:18123,53:19522,244:31479,245:33035,246:34956,247:40758,248:50596,249:57743,250:21033,251:22954,252:24637,253:26486,254:29003},stopsto:{1:57091,2:56497,16:55338,19:53586,20:53042,27:52325,29:49547,32:48069,33:48635,35:47554,36:47133,37:46745,38:46461,39:45849,40:45443,41:44833,42:44381,43:43786,44:43439,45:42783,46:42277,47:41833,48:41201,49:40236,50:39626,51:39112,52:38633,53:37669,244:26683,245:25127,246:23206,247:17404,248:7147,249:0,250:37129,251:35208,252:33525,254:29159}},21:{routeno:"177",from:"Kollupitiya",to:"Kaduwela",stopsfrom:{5:0,28:1076,29:1989,34:1299,78:5913,79:5570,80:550,81:4087,101:5057,280:6546,281:6977,282:8381,283:9532,284:9954,285:10989,286:13321,287:14506,288:16400,289:17218,290:20186},stopsto:{5:19102,34:17500,78:14405,79:14748,81:16231,82:16989,101:15261,280:13769,281:13338,282:11805,283:10654,284:10232,285:9197,286:6865,287:5680,288:3786,289:2968,290:0}},22:{routeno:"155",from:"Soysapura",to:"Mattakkuliya",stopsfrom:{7:11044,8:10135,9:9258,10:8689,19:19631,20:19087,24:12637,27:18370,29:15592,32:14108,35:13593,201:8159,202:7615,203:7228,204:7059,205:6849,206:6618,207:5902,208:5246,209:4907,210:4428,211:3895,212:2211,213:1435,214:1076,215:0},stopsto:{7:5531,8:7039,9:8315,10:8884,19:0,20:544,27:1862,29:2983,30:3423,31:3874,35:4658,201:9414,202:9958,203:10345,204:10514,205:10724,206:10955,207:11671,208:12327,209:12666,210:13145,211:13678,212:15362,213:16138,214:16497,215:17573}},23:{routeno:"155",from:"Mount Lavinia",to:"Mattakkuliya",stopsfrom:{7:7149,8:6240,9:5363,10:4794,19:15736,20:15192,24:8742,27:14475,29:11697,32:10213,35:9698,201:4264,202:3720,203:3333,204:3164,205:2954,206:2723,207:2007,208:1351,209:1012,210:533,211:0},stopsto:{7:5531,8:7039,9:8315,10:8884,19:0,20:544,27:1862,29:2983,30:3423,31:3874,35:4658,201:9414,202:9958,203:10345,204:10514,205:10724,206:10955,207:11671,208:12327,209:12666,210:13145,211:13678}},24:{routeno:"140",from:"Kollupitiya",to:"Wellampitiya",stopsfrom:{5:0,27:2546,28:1076,77:4366,80:550},stopsto:{5:5520,27:2296,28:3849,77:0}},25:{routeno:"175",from:"Kollupitiya",to:"Kohilawatte",stopsfrom:{5:0,27:2546,28:1076,80:550,100:5403},stopsto:{5:6081,27:2857,28:4410,100:0}},26:{routeno:"119",from:"Dehiwala",to:"Maharagama",stopsfrom:{53:0,207:8242,235:3347,274:5610,275:6310,276:7134},stopsto:{53:8242,207:0,235:4895,274:2632,275:1932,276:1108}},27:{routeno:"174",from:"Kottawa",to:"Borella",stopsfrom:{100:14467,251:1683,252:0,280:12882,281:12451,282:11047,283:9896,284:9474,291:5620,292:7493},stopsto:{100:0,251:12784,252:14467,280:1585,281:2016,282:3420,283:4571,284:4993,291:8847,292:6974}},28:{routeno:"163",from:"Dehiwala",to:"Battaramulla",stopsfrom:{45:4792,203:1326,204:1157,205:947,206:716,207:0,231:3756,277:2929,278:5708,279:7485,282:10193,283:11344,284:11766},stopsto:{45:7195,203:10781,204:10950,205:11160,206:11391,207:12107,231:8301,277:9178,278:6037,279:4260,282:1573,283:422,284:0}},29:{routeno:"176",from:"Karagampitiya",to:"Hettiyawatte",stopsfrom:{45:5222,76:15565,77:16125,78:12791,79:13134,100:14033,101:13647,231:4116,271:7771,272:8222,273:9983,276:0,277:3239,278:6380,280:12158,281:11727},stopsto:{45:10903,76:560,77:0,78:3334,79:2991,100:2092,101:2478,231:12009,271:8354,272:7903,273:6142,276:16125,277:12886,278:9745,280:3967,281:4398}},30:{routeno:"103",from:"Narahenpita",to:"Fort",stopsfrom:{1:7565,2:8174,76:4611,77:5459,90:0,100:3079,101:2693,102:2285},stopsto:{1:594,2:0,76:2405,77:1845,90:6477,100:3937,101:4323,102:4620}},31:{routeno:"187",from:"Fort",to:"Airport",stopsfrom:{2:0,74:2890,75:2221,295:4893,296:5568,297:8207,298:9455,299:13190,300:16190,301:18135,302:19828,303:31121},stopsto:{2:31121,74:28231,75:28900,295:26228,296:25553,297:22914,298:21666,299:17931,300:14931,301:12986,302:11293,303:0}},32:{routeno:"190",from:"Pettah",to:"Meegoda",stopsfrom:{1:0,2:609,16:1385,19:3208,20:3752,27:5070,100:7011,245:28050,246:29971,258:21811,280:8596,281:9027,282:10431,283:11582,284:12004,285:13039,286:15371,287:16556},stopsto:{1:30706,2:30112,16:28953,19:27201,20:26657,27:25940,100:23083,245:1921,246:0,258:8160,280:21504,281:21073,282:19540,283:18389,284:17967,285:16932,286:14600,287:13415}},33:{routeno:"336",from:"Kottawa",to:"Malabe",stopsfrom:{252:0,287:8700},stopsto:{252:8700,287:0}},34:{routeno:"17",from:"Kandy",to:"Panadura",stopsfrom:{45:17427,203:21013,204:21182,205:21392,206:21623,207:22339,208:22995,209:23334,210:23813,211:24346,212:26030,213:26806,214:27165,215:28241,216:29094,217:30198,218:31832,219:39912,231:18533,277:19410,278:16269,279:14492,282:11805,283:10654,284:10232,285:9197,286:6865,287:5680,288:3786,289:2968,290:0},stopsto:{45:22296,203:18830,204:18661,205:18451,206:18220,207:17504,208:16848,209:16509,210:16030,211:15497,212:13813,213:13037,214:12678,215:11602,216:10749,217:9645,218:8060,219:0,231:21260,277:20433,278:23212,279:24989,282:27697,283:28848,284:29270,285:30305,286:32637,287:33822,288:35716,289:36534,290:39502}}},groups:{1:{routes:["1","2","3","4","5","6"],stops:[1,2,25,26,27,28,29,30,31,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,250,251,252,32,33,253,254,259,260,255,256,257,258]},2:{routes:["7","8"],stops:[1,2,3,4,5,7,8,9,10,23,24,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,6]},3:{routes:["9"],stops:[1,2,5,7,8,9,10,21,22,23,24,25,26,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,6]},4:{routes:["10","11"],stops:[7,8,9,10,31,35,100,101,102,103,104,201,202,203,204,205,206,207,208,209,210,211,212,213,214,220,24,30,32,33]},5:{routes:["12","13","14"],stops:[1,2,16,19,20,27,29,30,31,35,36,37,38,39,40,83,230,231,232,233,234,235,236,237,238,239,240,241,242,32,33,85,87]},6:{routes:["15"],stops:[10,41,86,87,88,89,90,40,84,85]},7:{routes:["16"],stops:[37,38,39,85,87,90,100,101,102,230,231,40,83]},8:{routes:["17","18","19","20"],stops:[1,2,16,19,20,27,28,29,30,31,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,244,245,246,247,250,251,252,253,254,32,33,248,249]},9:{routes:["21"],stops:[5,28,29,34,78,79,80,81,101,280,281,282,283,284,285,286,287,288,289,290,82]},10:{routes:["22","23"],stops:[7,8,9,10,19,20,24,27,29,32,35,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,30,31]},11:{routes:["24"],stops:[5,27,28,77,80]},12:{routes:["25"],stops:[5,27,28,80,100]},13:{routes:["26"],stops:[53,207,235,274,275,276]},14:{routes:["27"],stops:[100,251,252,280,281,282,283,284,291,292]},15:{routes:["28"],stops:[45,203,204,205,206,207,231,277,278,279,282,283,284]},16:{routes:["29"],stops:[45,76,77,78,79,100,101,231,271,272,273,276,277,278,280,281]},17:{routes:["30"],stops:[1,2,76,77,90,100,101,102]},18:{routes:["31"],stops:[2,74,75,295,296,297,298,299,300,301,302,303]},19:{routes:["32"],stops:[1,2,16,19,20,27,100,245,246,258,280,281,282,283,284,285,286,287]},20:{routes:["33"],stops:[252,287]},21:{routes:["34"],stops:[45,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,231,277,278,279,282,283,284,285,286,287,288,289,290]}}};exports.default=Buses},{}],3:[function(require,module,exports){(function(global){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var _createClass=function(){function defineProperties(target,props){for(var i=0;i0){routes=this.props.routes.map(function(r,index){return _react2.default.createElement(RenderOption,{key:index,first:index===0,route:r,router:this.props.router})}.bind(this))}return _react2.default.createElement("div",null,routes)}}]);return DisplayRoutes}(_react.Component);exports.default=DisplayRoutes}).call(this,typeof global!=="undefined"?global:typeof self!=="undefined"?self:typeof window!=="undefined"?window:{})},{}],4:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var _buses=require("./buses");var _buses2=_interopRequireDefault(_buses);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{"default":obj}}var Router=function Router(){this.buses=_buses2.default;this.penalty=5e3};Router.prototype.unique=function(array){return array.filter(function(item,pos){return array.indexOf(item)==pos})};Router.prototype.intersect=function(array1,array2){return this.unique(array1.filter(function(n){return array2.indexOf(n)!=-1}))};Router.prototype.getDistance=function(route,from,to){if(!this.buses.routes.hasOwnProperty(route)){return 1/0}var fromdist=this.buses.routes[route].stopsfrom[to]-this.buses.routes[route].stopsfrom[from];var todist=this.buses.routes[route].stopsto[to]-this.buses.routes[route].stopsto[from];return fromdist>0?fromdist:todist>0?todist:1/0};Router.prototype.reachable=function(route,from,to){return this.buses.routes.hasOwnProperty(route)&&(this.buses.routes[route].stopsfrom[to]>this.buses.routes[route].stopsfrom[from]||this.buses.routes[route].stopsto[to]>this.buses.routes[route].stopsto[from])};Router.prototype.findReachableStops=function(route,stop){var stops=[];if(this.buses.routes.hasOwnProperty(route)&&this.buses.routes[route].stopsfrom.hasOwnProperty(stop)){var startdist=this.buses.routes[route].stopsfrom[stop];for(var s in this.buses.routes[route].stopsfrom){if(this.buses.routes[route].stopsfrom[s]>startdist){stops.push(s)}}}if(this.buses.routes.hasOwnProperty(route)&&this.buses.routes[route].stopsto.hasOwnProperty(stop)){var startdist=this.buses.routes[route].stopsto[stop];for(var s in this.buses.routes[route].stopsto){if(this.buses.routes[route].stopsto[s]>startdist){stops.push(s)}}}return this.unique(stops)};Router.prototype.findStopRoutes=function(stop){var routes=[];for(var r in this.buses.routes){if(this.buses.routes[r].stopsfrom.hasOwnProperty(stop)||this.buses.routes[r].stopsto.hasOwnProperty(stop)){routes.push(r)}}return this.unique(routes)};Router.prototype.findSingleRoutes=function(from,to){var _self=this;var fromRoutes=this.findStopRoutes(from);var toRoutes=this.findStopRoutes(to);return this.intersect(fromRoutes,toRoutes).filter(function(r){return _self.reachable(r,from,to)})};Router.prototype.findRoutes=function(from,to){var limit=arguments.length<=2||arguments[2]===undefined?5:arguments[2];var fromRoutes=this.findStopRoutes(from);var toRoutes=this.findStopRoutes(to);var singleRoute=this.findSingleRoutes(from,to);if(singleRoute.length>0){singleRoute=singleRoute.sort(function(a,b){return a.distance-b.distance});var distance=this.getDistance(singleRoute[0],from,to);return[{from:from,routes:[{routes:singleRoute.slice(0,5),distance:distance}],changes:[],to:to,distance:distance}]}else{var fromStops=[],toStops=[],distances,multiRoutes=[],_self=this;fromRoutes.forEach(function(fr){fromStops=fromStops.concat(_self.findReachableStops(fr,from))});toRoutes.forEach(function(tr){toStops=toStops.concat(_self.findReachableStops(tr,to))});var common=this.intersect(fromStops,toStops);common.forEach(function(c){var toc=_self.findSingleRoutes(from,c);var fromc=_self.findSingleRoutes(c,to);if(toc.length>0&&fromc.length>0){distances=[_self.getDistance(toc[0],from,c),_self.getDistance(fromc[0],c,to)];multiRoutes.push({from:from,routes:[{routes:toc,distance:distances[0]},{routes:fromc,distance:distances[1]}],changes:[c],to:to,distance:distances[0]+distances[1]})}});if(common.length<3){fromStops.forEach(function(fs){toStops.forEach(function(ts){var tots=_self.findSingleRoutes(fs,ts);if(tots.length>0){var tofs=_self.findSingleRoutes(from,fs);var fromts=_self.findSingleRoutes(ts,to);if(tofs.length>0&&fromts.length>0){distances=[_self.getDistance(tofs[0],from,fs),_self.getDistance(tots[0],fs,ts),_self.getDistance(fromts[0],ts,to)];multiRoutes.push({from:from,routes:[{routes:tofs,distance:distances[0]},{routes:tots,distance:distances[1]},{routes:fromts,distance:distances[2]}],changes:[fs,ts],to:to,distance:distances[0]+distances[1]+distances[2]})}}})})}if(multiRoutes.length>0){multiRoutes=multiRoutes.filter(function(n){return!Number.isNaN(n.distance)}).sort(function(a,b){return(a.routes.length-b.routes.length)*_self.penalty+(a.distance-b.distance)});return multiRoutes.slice(0,limit)}}};Router.prototype.getPlaceDetails=function(pid){return this.buses.places[pid]};Router.prototype.getRouteDetails=function(id){return this.buses.routes[id]};Router.prototype.getAllPlaces=function(){var places=[];for(var p in this.buses.places){places.push({id:p,name:this.buses.places[p].name})}return places};Router.prototype.deg2rad=function(deg){return deg*(Math.PI/180)};Router.prototype.haversine=function(lat1,lon1,lat2,lon2){var R=6371;var dLat=this.deg2rad(lat2-lat1);var dLon=this.deg2rad(lon2-lon1);var a=Math.sin(dLat/2)*Math.sin(dLat/2)+Math.cos(this.deg2rad(lat1))*Math.cos(this.deg2rad(lat2))*Math.sin(dLon/2)*Math.sin(dLon/2);var c=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));return Math.floor(R*c*1e3)};Router.prototype.getNearestPlace=function(lat,lon){var nearby=[],place;for(var p in this.buses.places){if(Math.abs(this.buses.places[p].lat-lat)<.1&&Math.abs(this.buses.places[p].lon-lon)<.1){place=this.buses.places[p];place.id=p;place.distance=this.haversine(place.lat,place.lon,lat,lon);nearby.push(place)}}return nearby.sort(function(a,b){return b.distance-a.distance}).pop()};exports.default=Router},{"./buses":2}],5:[function(require,module,exports){(function(global){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var _createClass=function(){function defineProperties(target,props){for(var i=0;i