├── .gitignore ├── .jshintrc ├── .npmignore ├── Jakefile.js ├── LICENSE ├── README.md ├── bower.json ├── build ├── build.js ├── deps.js ├── hintrc.js └── r360-include.js ├── dist ├── r360-src.js └── r360.js ├── package.json ├── r360.png ├── src ├── basemaps │ └── Basemaps.js ├── copyright.js ├── extension │ ├── google │ │ ├── layer │ │ │ └── GoogleMapsPolygonLayer.js │ │ └── util │ │ │ └── GoogleMapsUtil.js │ └── leaflet │ │ ├── layer │ │ ├── CanvasLayer.js │ │ ├── LeafletPolygonLayer.js │ │ └── TileLayer.js │ │ └── util │ │ └── LeafletUtil.js ├── geometry │ ├── crs │ │ ├── CRS.EPSG3857.js │ │ ├── CRS.Earth.js │ │ └── CRS.js │ ├── projection │ │ └── Projection.SphericalMercator.js │ ├── transformation │ │ └── Transformation.js │ └── types │ │ ├── bounds │ │ ├── Bounds.js │ │ └── LatLngBounds.js │ │ ├── linestring │ │ └── LineString.js │ │ ├── point │ │ ├── LatLng.js │ │ └── Point.js │ │ ├── polygon │ │ ├── MultiPolygon.js │ │ └── Polygon.js │ │ └── route │ │ ├── Route.js │ │ └── RouteSegment.js ├── r360-defaults.js ├── r360.js ├── rest │ ├── polygons │ │ └── PolygonService.js │ ├── routes │ │ └── RouteService.js │ └── time │ │ └── TimeService.js └── util │ ├── Browser.js │ ├── Class.js │ ├── DomUtil.js │ ├── PolygonUtil.js │ ├── SvgUtil.js │ ├── TravelOptions.js │ └── Util.js └── test ├── TravelOptionsSpec.js ├── UtilSpec.js ├── dummydata ├── transit-polygon.js └── transit-route.js └── karma.conf.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | lib-esm/ 4 | lib/ 5 | _bundles/ 6 | _doc/ 7 | _src/ 8 | debug/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "browser": true, 4 | "curly": false, 5 | "eqeqeq": true, 6 | "esnext": true, 7 | "latedef": false, 8 | "noarg": true, 9 | "node": true, 10 | "shadow": true, 11 | "strict": false, 12 | "undef": true, 13 | "unused": true, 14 | "multistr": true, 15 | "loopfunc": true, 16 | "globals": { 17 | "angular": false, 18 | "L": false, 19 | "r360": false, 20 | "$": false, 21 | "_": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | debug 3 | test 4 | src 5 | Gruntfile.js 6 | bower.json 7 | Jakefile.js 8 | -------------------------------------------------------------------------------- /Jakefile.js: -------------------------------------------------------------------------------- 1 | /* 2 | r360 building, testing and linting scripts. 3 | 4 | To use, install Node, then run the following commands in the project root: 5 | 6 | npm install -g jake 7 | npm install 8 | 9 | To check the code for errors and build r360 from source, run "jake". 10 | To run the tests, run "jake test". 11 | 12 | For a custom build, open build/build.html in the browser and follow the instructions. 13 | */ 14 | 15 | var build = require('./build/build.js'), 16 | version = require('./src/r360.js').version; 17 | 18 | function hint(msg, paths) { 19 | return function () { 20 | console.log(msg); 21 | jake.exec('node node_modules/jshint/bin/jshint -c ' + paths, 22 | {printStdout: true}, function () { 23 | console.log('\tCheck passed.\n'); 24 | complete(); 25 | }); 26 | }; 27 | } 28 | 29 | desc('Check r360 source for errors with JSHint'); 30 | task('lint', {async: true}, hint('Checking for JS errors...', 'build/hintrc.js src')); 31 | 32 | desc('Check r360 specs source for errors with JSHint'); 33 | task('lintspec', {async: true}, hint('Checking for specs JS errors...', 'spec/spec.hintrc.js spec/suites')); 34 | 35 | desc('Combine and compress r360 source files'); 36 | task('build', {async: true}, function (compsBase32, buildName) { 37 | var v; 38 | 39 | jake.exec('git log -1 --pretty=format:"%h"', {}, function () { 40 | build.build(complete, v, compsBase32, buildName); 41 | 42 | }).on('stdout', function (data) { 43 | v = version + ' (' + data.toString() + ')'; 44 | }); 45 | }); 46 | 47 | desc('Run PhantomJS tests'); 48 | task('test', ['lint', 'lintspec'], {async: true}, function () { 49 | build.test(complete); 50 | }); 51 | 52 | task('default', ['test', 'build']); 53 | 54 | jake.addListener('complete', function () { 55 | process.exit(); 56 | }); 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016, Daniel Gerber and Henning Hollburg 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Important notice: Route360° is now called Targomo. The libraries here will no longer be maintained. We will keep them here for legacy purposes but for future use please visit: [github.com/targomo/targomo-js](https://github.com/targomo/targomo-js) For support on Leaflet and GoogleMaps please visit [targomo-js-leaflet](https://github.com/targomo/targomo-js-leaflet) and [targomo-js-googlemaps](https://github.com/targomo/targomo-js-googlemaps) 2 | 3 | # r360-js 4 | 5 | 6 | ## API-Key 7 | Get your API key [here ](https://www.targomo.com/developers/pricing/). 8 | 9 | The Route360° JavaScript API is a modern open-source JavaScript library designed for Leaflet's mobile-friendly interactive maps. It is developed by Henning Hollburg and Daniel Gerber from the Motion Intelligence GmbH. 10 | 11 | 12 | # Features 13 | 14 | * Generate polygons which represent the area which is reachable from a given source point 15 | * Supported for **walk**, **car**, **bike** and **transit** routing 16 | * A number of predefined map controls (travel time slider, date and time chooser, travel type chooser, etc.) 17 | * Detailed routing information from source to target (travel time, transit trips, etc.) 18 | * Get routing information for hundreds of POIs in a single request in milliseconds 19 | * Support for elevation data 20 | 21 | # Demonstration and Usage 22 | A demonstration of the library's features, as well as detailed coding examples can be found [here](https://www.targomo.com/developers/documentation/javascript/code_example/). You can see what the service is capable of at the technology [demo](https://app.targomo.com/demo/#!/), just select the country you want to test. 23 | 24 | ![Route360](r360.png) 25 | 26 | 27 | # Build 28 | `npm run build` 29 | 30 | # Npm 31 | You can use our project with npm 32 | 33 | npm install route360 --save 34 | 35 | # Bower 36 | You can use our project in bower 37 | 38 | bower install route360 --save 39 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "route360", 3 | "authors": [ 4 | "gerber@motionintelligence.net", 5 | "hollburg@motionintelligence.net", 6 | "jan.silbersiepe@motionintelligence.net" 7 | ], 8 | "description": "Route360° JavaScript API", 9 | "main": "dist/r360-src.js", 10 | "keywords": [ 11 | "r360", 12 | "route360", 13 | "javascript", 14 | "polygon", 15 | "routing" 16 | ], 17 | "dependencies": { 18 | "leaflet" : "0.7.7", 19 | "jquery" : "1.11.3" 20 | }, 21 | "homepage": "http://developers.route360.net/", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests", 28 | ".*", 29 | "CHANGELOG.json", 30 | "FAQ.md", 31 | "debug", 32 | "spec", 33 | "src", 34 | "build" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | jshint = require('jshint'), 3 | UglifyJS = require('uglify-js'), 4 | zlib = require('zlib'), 5 | 6 | deps = require('./deps.js').deps; 7 | 8 | function getFiles(module) { 9 | var memo = {}, 10 | comps; 11 | 12 | var moduleDeps = []; 13 | if (module == 'core') moduleDeps = deps.core.src; 14 | if (module == 'leaflet') moduleDeps = deps.leaflet.src; 15 | if (module == 'google') moduleDeps = deps.google.src; 16 | 17 | for (var i = 0 ; i < moduleDeps.length ; i++ ) { 18 | memo[moduleDeps[i]] = true; 19 | } 20 | 21 | var files = []; 22 | 23 | for (var src in memo) { 24 | files.push('src/' + src); 25 | } 26 | 27 | return files; 28 | } 29 | 30 | exports.getFiles = getFiles; 31 | 32 | function getSizeDelta(newContent, oldContent, fixCRLF) { 33 | if (!oldContent) { 34 | return ' (new)'; 35 | } 36 | if (newContent === oldContent) { 37 | return ' (unchanged)'; 38 | } 39 | if (fixCRLF) { 40 | newContent = newContent.replace(/\r\n?/g, '\n'); 41 | oldContent = oldContent.replace(/\r\n?/g, '\n'); 42 | } 43 | var delta = newContent.length - oldContent.length; 44 | 45 | return delta === 0 ? '' : ' (' + (delta > 0 ? '+' : '') + delta + ' bytes)'; 46 | } 47 | 48 | function loadSilently(path) { 49 | try { 50 | return fs.readFileSync(path, 'utf8'); 51 | } catch (e) { 52 | return null; 53 | } 54 | } 55 | 56 | function combineFiles(files) { 57 | var content = ''; 58 | for (var i = 0, len = files.length; i < len; i++) { 59 | content += fs.readFileSync(files[i], 'utf8') + '\n\n'; 60 | } 61 | 62 | return content; 63 | } 64 | 65 | function bytesToKB(bytes) { 66 | return (bytes / 1024).toFixed(2) + ' KB'; 67 | }; 68 | 69 | function build(callback, version, buildName, module){ 70 | 71 | var files = getFiles(module); 72 | var combinedFiles = combineFiles(files); 73 | console.log('Concatenating and compressing ' + files.length + ' files for module ' + module + ' ... '); 74 | 75 | var oldSrc, srcDelta, pathPart, srcPath, newSrc; 76 | 77 | if ( module == 'core' ) { 78 | 79 | var copy = fs.readFileSync('src/copyright.js', 'utf8').replace('{VERSION}', version), 80 | intro = '(function (window, document, undefined) {', 81 | outro = '}(window, document));\n\n', 82 | newSrc = copy + intro + combinedFiles + outro, 83 | 84 | pathPart = 'dist/r360' + (buildName ? '-' + buildName : ''), 85 | srcPath = pathPart + '-src.js', 86 | 87 | oldSrc = loadSilently(srcPath), 88 | srcDelta = getSizeDelta(newSrc, oldSrc, true); 89 | } 90 | 91 | console.log('\tUncompressed: ' + bytesToKB(newSrc.length) + srcDelta); 92 | 93 | // if (newSrc !== oldSrc) { 94 | fs.writeFileSync(srcPath, newSrc); 95 | console.log('\tSaved to ' + srcPath); 96 | // } 97 | 98 | var path = pathPart + '.js', 99 | oldCompressed = loadSilently(path), 100 | newCompressed = copy + UglifyJS.minify(newSrc, { 101 | warnings: true, 102 | fromString: true 103 | }).code, 104 | delta = getSizeDelta(newCompressed, oldCompressed); 105 | 106 | console.log('\tCompressed: ' + bytesToKB(newCompressed.length) + delta); 107 | 108 | var newGzipped, 109 | gzippedDelta = ''; 110 | 111 | function done() { 112 | // if (newCompressed !== oldCompressed) { 113 | fs.writeFileSync(path, newCompressed); 114 | console.log('\tSaved to ' + path); 115 | // } 116 | console.log('\tGzipped: ' + bytesToKB(newGzipped.length) + gzippedDelta); 117 | callback(); 118 | } 119 | 120 | // compress the new version 121 | zlib.gzip(newCompressed, function (err, gzipped) { 122 | if (err) { return; } 123 | newGzipped = gzipped; 124 | // if (oldCompressed && (oldCompressed !== newCompressed)) { 125 | // compress the old version 126 | zlib.gzip(oldCompressed, function (err, oldGzipped) { 127 | if (err) { return; } 128 | gzippedDelta = getSizeDelta(gzipped, oldGzipped); 129 | 130 | fs.writeFileSync(path, newCompressed); 131 | console.log('\tSaved to ' + path); 132 | console.log('\tGzipped: ' + bytesToKB(newGzipped.length) + gzippedDelta); 133 | // done(); 134 | }); 135 | // } else { 136 | // done(); 137 | // } 138 | }); 139 | } 140 | 141 | exports.build = function (callback, version, buildName) { 142 | 143 | // fs.unlink('dist/r360-core.js'); 144 | // fs.unlink('dist/r360-core-src.js'); 145 | // fs.unlink('dist/r360-leaflet.js'); 146 | // fs.unlink('dist/r360-leaflet-src.js'); 147 | // fs.unlink('dist/r360-google.js'); 148 | // fs.unlink('dist/r360-google-src.js'); 149 | 150 | build(callback, version, buildName, 'core'); 151 | console.log('---------------------------------------------------------'); 152 | // build(callback, version, buildName, 'google'); 153 | // console.log('---------------------------------------------------------'); 154 | // build(callback, version, buildName, 'leaflet'); 155 | }; 156 | 157 | exports.test = function(complete, fail) { 158 | var karma = require('karma'), 159 | testConfig = {configFile : __dirname + '/../spec/karma.conf.js'}; 160 | 161 | testConfig.browsers = ['PhantomJS']; 162 | 163 | function isArgv(optName) { 164 | return process.argv.indexOf(optName) !== -1; 165 | } 166 | 167 | if (isArgv('--chrome')) { 168 | testConfig.browsers.push('Chrome'); 169 | } 170 | if (isArgv('--safari')) { 171 | testConfig.browsers.push('Safari'); 172 | } 173 | if (isArgv('--ff')) { 174 | testConfig.browsers.push('Firefox'); 175 | } 176 | if (isArgv('--ie')) { 177 | testConfig.browsers.push('IE'); 178 | } 179 | 180 | if (isArgv('--cov')) { 181 | testConfig.preprocessors = { 182 | 'src/**/*.js': 'coverage' 183 | }; 184 | testConfig.coverageReporter = { 185 | type : 'html', 186 | dir : 'coverage/' 187 | }; 188 | testConfig.reporters = ['coverage']; 189 | } 190 | 191 | console.log('Running tests...'); 192 | 193 | karma.server.start(testConfig, function(exitCode) { 194 | if (!exitCode) { 195 | console.log('\tTests ran successfully.\n'); 196 | complete(); 197 | } else { 198 | process.exit(exitCode); 199 | } 200 | }); 201 | }; 202 | -------------------------------------------------------------------------------- /build/deps.js: -------------------------------------------------------------------------------- 1 | var deps = { 2 | core : { 3 | src: ['r360.js', 4 | 'r360-defaults.js', 5 | 'geometry/types/bounds/Bounds.js', 6 | 'geometry/types/bounds/LatLngBounds.js', 7 | 'geometry/types/point/Point.js', 8 | 'geometry/types/point/LatLng.js', 9 | 'util/Browser.js', 10 | 'util/Class.js', 11 | 'util/PolygonUtil.js', 12 | 'util/SvgUtil.js', 13 | 'util/Util.js', 14 | 'util/DomUtil.js', 15 | 'util/TravelOptions.js', 16 | 'rest/polygons/PolygonService.js', 17 | 'rest/routes/RouteService.js', 18 | 'rest/time/TimeService.js', 19 | 'geometry/projection/Projection.SphericalMercator.js', 20 | 'geometry/transformation/Transformation.js', 21 | 'geometry/crs/CRS.js', 22 | 'geometry/crs/CRS.Earth.js', 23 | 'geometry/crs/CRS.EPSG3857.js', 24 | 'geometry/types/polygon/Polygon.js', 25 | 'geometry/types/polygon/MultiPolygon.js', 26 | 'geometry/types/linestring/LineString.js', 27 | 'geometry/types/route/RouteSegment.js', 28 | 'geometry/types/route/RouteSegment.js', 29 | 'geometry/types/route/Route.js', 30 | 'basemaps/Basemaps.js', 31 | 'extension/leaflet/layer/LeafletPolygonLayer.js', 32 | 'extension/leaflet/layer/CanvasLayer.js', 33 | 'extension/leaflet/layer/TileLayer.js', 34 | 'extension/leaflet/util/LeafletUtil.js', 35 | 'extension/google/layer/GoogleMapsPolygonLayer.js', 36 | 'extension/google/util/GoogleMapsUtil.js' 37 | ], 38 | desc: 'This package contains all classes which are not dependent on any online map library like leaflet or google maps. ' 39 | } 40 | }; 41 | 42 | if (typeof exports !== 'undefined') { 43 | exports.deps = deps; 44 | } 45 | -------------------------------------------------------------------------------- /build/hintrc.js: -------------------------------------------------------------------------------- 1 | { 2 | // environment 3 | "browser": true, 4 | "node": true, 5 | "globals": { 6 | "L": true, 7 | "define": true 8 | }, 9 | "strict": false, 10 | "es3": true, 11 | 12 | // code style 13 | "bitwise": true, 14 | "camelcase": true, 15 | "curly": true, 16 | "eqeqeq": true, 17 | "forin": false, 18 | "immed": true, 19 | "latedef": true, 20 | "newcap": true, 21 | "noarg": true, 22 | "noempty": true, 23 | "nonew": true, 24 | "undef": true, 25 | "unused": true, 26 | "quotmark": "single", 27 | 28 | // whitespace 29 | "indent": 4, 30 | "trailing": true, 31 | "white": true, 32 | "smarttabs": true, 33 | "maxlen": 120 34 | 35 | // code simplicity - not enforced but nice to check from time to time 36 | // "maxstatements": 20, 37 | // "maxcomplexity": 5 38 | // "maxparams": 4, 39 | // "maxdepth": 4 40 | } 41 | -------------------------------------------------------------------------------- /build/r360-include.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function getFiles() { 3 | var memo = {}, 4 | files = [], 5 | i, src; 6 | 7 | function addFiles(srcs) { 8 | for (var j = 0, len = srcs.length; j < len; j++) { 9 | memo[srcs[j]] = true; 10 | } 11 | } 12 | 13 | for (i in deps) { 14 | addFiles(deps[i].src); 15 | } 16 | 17 | for (src in memo) { 18 | files.push(src); 19 | } 20 | 21 | return files; 22 | } 23 | var scripts = getFiles(); 24 | 25 | function getSrcUrl() { 26 | var scripts = document.getElementsByTagName('script'); 27 | for (var i = 0; i < scripts.length; i++) { 28 | var src = scripts[i].src; 29 | if (src) { 30 | var res = src.match(/^(.*)r360-include\.js$/); 31 | if (res) { 32 | return res[1] + '../src/'; 33 | } 34 | } 35 | } 36 | } 37 | 38 | var path = getSrcUrl(); 39 | for (var i = 0; i < scripts.length; i++) { 40 | document.writeln(""); 41 | } 42 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "route360", 3 | "version": "0.5.3", 4 | "description": "The Route360° JavaScript API for Motion Intelligence's time-based access mapping service.", 5 | "main": "dist/r360.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "jake build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/route360/r360-js.git" 16 | }, 17 | "keywords": [ 18 | "map", 19 | "gis", 20 | "leaflet", 21 | "isochrone" 22 | ], 23 | "author": "Motion Intelligence GmbH", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/route360/r360-js/issues" 27 | }, 28 | "homepage": "https://developers.route360.net", 29 | "devDependencies": { 30 | "jake": "^8.0.15", 31 | "jshint": "^2.9.5", 32 | "uglify-js": "~2.7.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /r360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/route360/r360-js/a569cee814ceba0fb404b79040ddb8359a7045ce/r360.png -------------------------------------------------------------------------------- /src/basemaps/Basemaps.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.basemapsLookup = { 5 | 'bright': 'osm-bright-gl-style', 6 | 'light': 'positron-gl-style', 7 | 'dark': 'dark-matter-gl-style', 8 | 'blues': 'fiord-color-gl-style', 9 | 'basic': 'klokantech-basic-gl-style' 10 | }; 11 | 12 | /** 13 | * [r360.getBasemapList returns an array of Route360 basemap names. ] 14 | * @return {basemapList} Array [returns array of basemaps names] 15 | */ 16 | r360.getBasemapList = function () { 17 | return Object.keys(r360.basemapsLookup); 18 | }; 19 | 20 | /** 21 | * [r360.getGLStyle returns style url for mapbox-gl style. ] 22 | * @param {stylename} String [accepts string of valid Route360 style name] 23 | * @param {apikey} String [accepts string of Route360 apikey] 24 | * @return {styleUrl} String [returns url for mapbox-gl style] 25 | */ 26 | r360.getGLStyle = function (stylename, apikey) { 27 | if (!stylename && !r360.basemapsLookup[stylename]) { 28 | throw new Error('valid style name required to access Route360 basemap'); 29 | } 30 | if (!apikey) { 31 | throw new Error('apikey required to access Route360 basemaps'); 32 | } 33 | 34 | return 'https://maps.route360.net/styles/' + r360.basemapsLookup[stylename] + '.json?key=' + apikey 35 | }; -------------------------------------------------------------------------------- /src/copyright.js: -------------------------------------------------------------------------------- 1 | /* 2 | Route360° JavaScript API {VERSION}, a JS library for leaflet.js and google maps API. http://route360.net 3 | (c) 2017 Henning Hollburg, Daniel Gerber and Jan Silbersiepe, (c) 2017 Motion Intelligence GmbH 4 | */ 5 | -------------------------------------------------------------------------------- /src/extension/google/layer/GoogleMapsPolygonLayer.js: -------------------------------------------------------------------------------- 1 | if (typeof google === 'object' && typeof google.maps === 'object') { 2 | 3 | GoogleMapsPolygonLayer.prototype = new google.maps.OverlayView(); 4 | 5 | function GoogleMapsPolygonLayer(map, options) { 6 | 7 | // set default parameters 8 | this.map = map; 9 | this.id = r360.Util.generateId(); 10 | this.inverse = false; 11 | this.topRight = { lat : -90, lng : -180 }; 12 | this.bottomLeft = { lat : +90, lng : +180 }; 13 | this.opacity = r360.config.defaultPolygonLayerOptions.opacity; 14 | this.strokeWidth = r360.config.defaultPolygonLayerOptions.strokeWidth; 15 | this.backgroundColor = r360.config.defaultPolygonLayerOptions.backgroundColor, 16 | this.backgroundOpacity = r360.config.defaultPolygonLayerOptions.backgroundOpacity, 17 | this.tolerance = r360.config.defaultPolygonLayerOptions.tolerance; 18 | this.extendWidthX = r360.config.defaultPolygonLayerOptions.strokeWidth / 2; 19 | this.extendWidthY = r360.config.defaultPolygonLayerOptions.strokeWidth / 2; 20 | 21 | // overwrite defaults with optional parameters 22 | if ( typeof options != 'undefined' ) { 23 | 24 | if ( typeof options.opacity != 'undefined') this.opacity = options.opacity; 25 | if ( typeof options.strokeWidth != 'undefined') this.strokeWidth = options.strokeWidth; 26 | if ( typeof options.inverse != 'undefined') this.inverse = options.inverse; 27 | if ( typeof options.tolerance != 'undefined') this.tolerance = options.tolerance; 28 | if ( typeof options.extendWidthX != 'undefined') this.extendWidthX = options.extendWidthX; 29 | if ( typeof options.extendWidthY != 'undefined') this.extendWidthY = options.extendWidthY; 30 | } 31 | 32 | // the div element containing all the data 33 | this.element = null; 34 | // this triggers the draw method 35 | this.setMap(this.map); 36 | // add the listeners for drag end zoom events 37 | this.addListener(); 38 | } 39 | 40 | /** 41 | * onAdd is called when the map's panes are ready and the overlay has been 42 | * added to the map. 43 | */ 44 | GoogleMapsPolygonLayer.prototype.onAdd = function() { 45 | 46 | // create the dom elemenet which hols old the svgs 47 | this.element = document.createElement('div'); 48 | this.element.id = 'r360-googlemaps-polygon-layer-canvas-in-' + this.id; 49 | 50 | // Add the element to the "overlayLayer" pane. 51 | this.getPanes().overlayLayer.appendChild(this.element); 52 | }; 53 | 54 | GoogleMapsPolygonLayer.prototype.getMapPixelBounds = function(){ 55 | 56 | var bottomLeft = r360.GoogleMapsUtil.googleLatlngToPoint(this.map, this.map.getBounds().getSouthWest(), this.map.getZoom()); 57 | var topRight = r360.GoogleMapsUtil.googleLatlngToPoint(this.map, this.map.getBounds().getNorthEast(), this.map.getZoom()); 58 | 59 | return { max : { x : topRight.x, y : bottomLeft.y }, min : { x : bottomLeft.x, y : topRight.y } }; 60 | }; 61 | 62 | GoogleMapsPolygonLayer.prototype.getPixelOrigin = function(){ 63 | 64 | var viewHalf = r360.PolygonUtil.divide({ x : this.map.getDiv().offsetWidth, y : this.map.getDiv().offsetHeight }, 2); 65 | var center = r360.GoogleMapsUtil.googleLatlngToPoint(this.map, this.map.getCenter(), this.map.getZoom()); 66 | 67 | return r360.PolygonUtil.roundPoint(r360.PolygonUtil.subtract(center, viewHalf.x, viewHalf.y)); 68 | }; 69 | 70 | /** 71 | * [getBoundingBox3857 returns a boundingbox (in web mercator) from the left bottom to the top right of this layer] 72 | * @return {type} [description] 73 | */ 74 | GoogleMapsPolygonLayer.prototype.getBoundingBox3857 = function(){ 75 | 76 | return this.multiPolygons[0].getBoundingBox3857(); 77 | }, 78 | 79 | /** 80 | * [getBoundingBox4326 returns a boundingbox (in wgs84) from the left bottom to the top right of this layer] 81 | * @return {type} [description] 82 | */ 83 | GoogleMapsPolygonLayer.prototype.getBoundingBox4326 = function(){ 84 | 85 | return this.multiPolygons[0].getBoundingBox4326(); 86 | }, 87 | 88 | 89 | GoogleMapsPolygonLayer.prototype.setInverse = function(inverse){ 90 | 91 | if ( this.inverse != inverse ) { 92 | 93 | this.inverse = inverse; 94 | this.draw(); 95 | } 96 | }; 97 | 98 | GoogleMapsPolygonLayer.prototype.createSvgData = function(polygon){ 99 | 100 | var svg = r360.SvgUtil.createSvgData(polygon, { 101 | bounds : r360.PolygonUtil.extendBounds(this.getMapPixelBounds(), this.extendWidthX, this.extendWidthY), 102 | scale : Math.pow(2, this.map.getZoom()) * 256, 103 | tolerance : this.tolerance, 104 | pixelOrigin : this.getPixelOrigin(), 105 | offset : {x:0,y:0} 106 | }); 107 | 108 | return svg; 109 | }; 110 | 111 | GoogleMapsPolygonLayer.prototype.setColors = function(colors) { 112 | if ( typeof this.multiPolygons == 'undefined' ) return; 113 | colors = colors; 114 | for ( var i = 0 ; i < this.multiPolygons.length ; i++){ 115 | var multipolygon = this.multiPolygons[i]; 116 | colors.forEach(function(colorSet) { 117 | if (colorSet.time == multipolygon.getTravelTime()) multipolygon.setColor(colorSet.color); 118 | }) 119 | } 120 | this.draw(); 121 | }; 122 | 123 | GoogleMapsPolygonLayer.prototype.setStrokeWidth = function(strokeWidth){ 124 | 125 | this.strokeWidth = strokeWidth; 126 | this.draw(); 127 | }; 128 | 129 | /** 130 | * [fitMap adjust the map to fit the complete polygon with maximum zoom level] 131 | * @return {type} [description] 132 | */ 133 | GoogleMapsPolygonLayer.prototype.fitMap = function(){ 134 | 135 | // we have to transform the r360.latLngBounds to google maps bounds since the map object 136 | // only knows the leaflet version 137 | var bounds = this.getBoundingBox4326(); 138 | var sw = bounds.getSouthWest(), ne = bounds.getNorthEast(); 139 | 140 | var gmBounds = new google.maps.LatLngBounds( 141 | new google.maps.LatLng(sw.lat, sw.lng), 142 | new google.maps.LatLng(ne.lat, ne.lng)); 143 | 144 | this.map.fitBounds(gmBounds); 145 | }; 146 | 147 | GoogleMapsPolygonLayer.prototype.draw = function(test) { 148 | 149 | if ( typeof this.multiPolygons !== 'undefined' && this.element != null ) { 150 | 151 | this.svgWidth = this.map.getDiv().offsetWidth; 152 | this.svgHeight = this.map.getDiv().offsetHeight; 153 | 154 | // calculate the offset in between map and svg in order to translate 155 | var svgPosition = $('#svg_' + this.id).offset(); 156 | var mapPosition = $(this.map.getDiv()).offset(); 157 | 158 | // if first time through, there is not yet an svg to get offset for 159 | if ( typeof svgPosition == 'undefined') { 160 | svgPosition = { 161 | top: this.svgHeight / 2, 162 | left: this.svgWidth / 2 163 | } 164 | } 165 | 166 | if ( typeof this.offset == 'undefined' ) 167 | this.offset = { x : 0 , y : 0 }; 168 | 169 | // adjust the offset after map panning / zooming 170 | if ( typeof svgPosition != 'undefined' ) { 171 | this.offset.x += (mapPosition.left - svgPosition.left); 172 | this.offset.y += (mapPosition.top - svgPosition.top); 173 | } 174 | 175 | // clear layer from previous drawings 176 | $('#'+ this.element.id).empty(); 177 | 178 | var gElements = []; 179 | 180 | // go through each multipolygon (represents each travel time) 181 | for ( var i = 0 ; i < this.multiPolygons.length ; i++){ 182 | 183 | var multiPolygon = this.multiPolygons[i], svgData = []; 184 | 185 | // add each polygon for the given travel time 186 | for ( var j = 0; j < multiPolygon.polygons.length; j++) 187 | svgData.push(this.createSvgData(multiPolygon.polygons[j])); 188 | 189 | if ( svgData.length != 0 ) 190 | gElements.push(r360.SvgUtil.getGElement(svgData, { 191 | color : !this.inverse ? multiPolygon.getColor() : 'black', 192 | opacity : !this.inverse ? 1 : multiPolygon.getOpacity(), 193 | strokeWidth : this.strokeWidth 194 | })); 195 | } 196 | 197 | var options = { 198 | id : this.id, 199 | offset : this.offset, 200 | svgHeight : this.svgHeight, 201 | svgWidth : this.svgWidth, 202 | backgroundColor : this.backgroundColor, 203 | backgroundOpacity : this.backgroundOpacity, 204 | opacity : this.opacity, 205 | strokeWidth : this.strokeWidth 206 | } 207 | 208 | // add the svg string to the container 209 | $('#'+ this.element.id).append(!this.inverse ? r360.SvgUtil.getNormalSvgElement(gElements, options) 210 | : r360.SvgUtil.getInverseSvgElement(gElements, options)); 211 | 212 | } 213 | }; 214 | 215 | GoogleMapsPolygonLayer.prototype.update = function(multiPolygons){ 216 | 217 | this.multiPolygons = multiPolygons; 218 | this.draw(); 219 | }; 220 | 221 | GoogleMapsPolygonLayer.prototype.addListener = function() { 222 | 223 | var map = this.map; 224 | var that = this; 225 | 226 | google.maps.event.addListener(map, 'zoom_changed', function () { 227 | that.onRemove(); 228 | google.maps.event.addListenerOnce(map, 'idle', function () { 229 | that.draw(); 230 | }); 231 | }); 232 | 233 | google.maps.event.addListener(map, 'dragend', function () { 234 | google.maps.event.addListenerOnce(map, 'idle', function () { 235 | that.draw(); 236 | }); 237 | }); 238 | 239 | google.maps.event.addDomListener(window, "resize", function() { 240 | google.maps.event.trigger(map, "resize"); 241 | that.draw(); 242 | }); 243 | }; 244 | 245 | // The onRemove() method will be called automatically from the API if 246 | // we ever set the overlay's map property to 'null'. 247 | GoogleMapsPolygonLayer.prototype.onRemove = function() { 248 | if (typeof this.element == 'undefined' || this.element == null) return; 249 | $('#' + this.element.id).empty(); 250 | }; 251 | 252 | r360.googleMapsPolygonLayer = function(map, options) { 253 | if (typeof this.element != 'undefined' || this.element != null) return; 254 | return new GoogleMapsPolygonLayer(map, options); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/extension/google/util/GoogleMapsUtil.js: -------------------------------------------------------------------------------- 1 | r360.GoogleMapsUtil = { 2 | 3 | /** 4 | * @param {google.maps.Map} map 5 | * @param {google.maps.LatLng} latlng 6 | * @param {int} z 7 | * @return {google.maps.Point} 8 | */ 9 | googleLatlngToPoint : function(map, latlng, z){ 10 | var normalizedPoint = map.getProjection().fromLatLngToPoint(latlng); // returns x,y normalized to 0~255 11 | var scale = Math.pow(2, z); 12 | return new google.maps.Point(normalizedPoint.x * scale, normalizedPoint.y * scale); 13 | }, 14 | 15 | /** 16 | * @param {google.maps.Map} map 17 | * @param {google.maps.Point} point 18 | * @param {int} z 19 | * @return {google.maps.LatLng} 20 | */ 21 | googlePointToLatlng : function(map, point, z){ 22 | var scale = Math.pow(2, z); 23 | var normalizedPoint = new google.maps.Point(point.x / scale, point.y / scale); 24 | var latlng = map.getProjection().fromPointToLatLng(normalizedPoint); 25 | return latlng; 26 | } 27 | }; -------------------------------------------------------------------------------- /src/extension/leaflet/layer/CanvasLayer.js: -------------------------------------------------------------------------------- 1 | if ( typeof L === 'object' ) { 2 | 3 | CanvasLayer = L.Class.extend({ 4 | 5 | /** 6 | * This methods initializes the polygon layer's stroke width and polygon opacity. 7 | * It uses the default values, and in case the options contain other values, the 8 | * default values are overwritten. 9 | * 10 | * @method send 11 | * 12 | * @param {Object} [options] The typical JS options array. 13 | * @param {Number} [options.opacity] Defines the opacity of the polygons. 14 | * Higher values mean that the polygon is less opaque. 15 | * @param {Number} [options.strokeWidth] Defines the strokewidth of the polygons boundaries. 16 | * Since we have degenerated polygons (they can have no area), the stroke width defines the 17 | * thickness of a polygon. Thicker polygons are not as informative as thinner ones. 18 | */ 19 | initialize: function (options) { 20 | 'use strict'; 21 | this.items = []; 22 | var that = this; 23 | 24 | that.mapZoomPowerLookup = []; 25 | 26 | for(var i = 0; i < 25; i++){ 27 | that.mapZoomPowerLookup[i] = Math.pow(2, i) * 256; 28 | } 29 | }, 30 | 31 | 32 | getItemBelowCursor : function(items, event){ 33 | var that = this; 34 | for(i = 0; i < items.length; i++){ 35 | if(that.containedByCanvas(items[i].currentPixel)){ 36 | 37 | // console.log("containedByCanvas") 38 | if(that.containedByIcon(items[i], event)){ 39 | 40 | return items[i]; 41 | } 42 | } 43 | } 44 | return null; 45 | }, 46 | 47 | containedByCanvas : function(pixel){ 48 | 49 | if ( pixel.x >= 0 && pixel.x <= this.canvasWidth ) 50 | if ( pixel.y >= 0 && pixel.y <= this.canvasHeight ) 51 | return true; 52 | 53 | return false; 54 | }, 55 | 56 | containedByIcon : function(item, event){ 57 | 58 | var newPageX = event.pageX - this.mapPosition.left; 59 | var newPageY = event.pageY - this.mapPosition.top; 60 | 61 | var diffX = newPageX - item.currentPixel.x; 62 | var diffY = newPageY - item.currentPixel.y; 63 | 64 | var ringSizes = this.getRingSizesByZoom(item.icon, this.map._zoom); 65 | 66 | if ( diffX > -ringSizes.outerRing ) 67 | if( diffY > -ringSizes.outerRing ) 68 | if ( diffX < ringSizes.outerRing + 12 ) 69 | if( diffY < ringSizes.outerRing + 12) 70 | return true; 71 | 72 | return false; 73 | }, 74 | 75 | /** 76 | * [getBoundingBox3857 returns a boundingbox (in web mercator) from the left bottom to the top right of this layer] 77 | * @return {type} [description] 78 | */ 79 | getBoundingBox3857 : function(){ 80 | 81 | }, 82 | 83 | /** 84 | * [getBoundingBox4326 returns a boundingbox (in wgs84) from the left bottom to the top right of this layer] 85 | * @return {type} [description] 86 | */ 87 | getBoundingBox4326 : function(){ 88 | 89 | }, 90 | 91 | /* 92 | * 93 | */ 94 | onAdd: function (map) { 95 | 96 | var that = this; 97 | that.map = map; 98 | that.id = $(that.map._container).attr("id") + "_" + r360.Util.generateId(); 99 | that.elementId = 'r360-leaflet-canvas-poi-layer-' + that.id + ' leaflet-zoom-hide'; 100 | that.poiCanvasId = "poi-canvas" + that.id; 101 | that.poiMarkerClickId = "poi_marker_click_" + that.id; 102 | that.poiMarkerHoverId = "poi_marker_hover_" + that.id; 103 | that.markerMainCanvasId = "canvas_"+ that.id; 104 | 105 | // create a DOM element with a unique ID to have multiple maps on one page 106 | that.element = L.DomUtil.create('div', that.elementId); 107 | $(that.element).attr("id", that.poiCanvasId); 108 | 109 | // we append the layer to the overlay pane at the last position 110 | that.map.getPanes().overlayPane.appendChild(that.element); 111 | 112 | // add a view redraw event listener for updating layer's position 113 | // zoom in/out, panning 114 | that.map.on('moveend', that.redraw, that); 115 | 116 | $( "#" + $(that.map._container).attr("id") ).on( "mousemove", function( event ) { 117 | that.clearMarkerHover(); 118 | $('.leaflet-overlay-pane').attr("style", "cursor: move; cursor: grab; cursor:-moz-grab; cursor:-webkit-grab;"); 119 | var item = that.getItemBelowCursor(that.items, event); 120 | if(item !== null){ 121 | item.onHover(); 122 | $('.leaflet-overlay-pane').css('cursor', 'pointer'); 123 | that.drawMarkerHover(item); 124 | } 125 | }); 126 | 127 | $( "#" + $(that.map._container).attr("id") ).on( "click", function( event ) { 128 | 129 | // console.log("click"); 130 | that.clearMarkerHover(); 131 | var item = that.getItemBelowCursor(that.items, event); 132 | if(item !== null){ 133 | item.onClick(); 134 | that.drawMarkerClick(item); 135 | } 136 | }); 137 | 138 | that.resetCanvas(); 139 | }, 140 | 141 | /** 142 | *Not sure we need this 143 | */ 144 | fitMap: function(options){ 145 | 146 | // we have to transform the r360.latLngBounds to a L.latLngBounds since the map object 147 | // only knows the leaflet version 148 | var bounds = this.getBoundingBox4326(); 149 | var sw = bounds.getSouthWest(), ne = bounds.getNorthEast(); 150 | 151 | this.map.fitBounds( 152 | L.latLngBounds(L.latLng({ lat : sw.lat, lng : sw.lng}), L.latLng({ lat : ne.lat, lng : ne.lng})), options); 153 | }, 154 | 155 | /** 156 | * here should be a method that clears the canvas 157 | maybe we dont evcen need this 158 | */ 159 | clearAndAddLayers : function(multiPolygons, fitMap, options){ 160 | 161 | this.clearLayers(); 162 | return this; 163 | }, 164 | 165 | /** 166 | * [addLayer description] 167 | * @param {type} multiPolygons [description] 168 | */ 169 | 170 | 171 | addItem : function(item){ 172 | this.items.push(item); 173 | 174 | item.coordinate = r360.Util.webMercatorToLeaflet(item.point); 175 | 176 | this.draw(item); 177 | }, 178 | 179 | addLatLngItem: function(item) { 180 | 181 | var p = r360.Util.latLngToWebMercator(item.point); 182 | p.x /= 6378137; 183 | p.y /= 6378137; 184 | 185 | this.items.push(item); 186 | item.coordinate = r360.Util.webMercatorToLeaflet(p); 187 | 188 | this.draw(item); 189 | 190 | return item; 191 | }, 192 | 193 | 194 | addLayer : function(poiSet) { 195 | 196 | this.poiSet = poiSet; 197 | var that = this; 198 | 199 | // paint them 200 | this.draw(); 201 | }, 202 | 203 | /** 204 | * [addTo Adds this layer to the given map] 205 | * @param {type} map [the leaflet map on which the layer should be drawn] 206 | */ 207 | addTo: function (map) { 208 | map.addLayer(this); 209 | return this; 210 | }, 211 | 212 | /** 213 | * [onRemove description] 214 | * @param {type} map [description] 215 | * @return {type} [description] 216 | */ 217 | onRemove: function (map) { 218 | 219 | // remove layer's DOM elements and listeners 220 | map.getPanes().overlayPane.removeChild(this.element); 221 | map.off('viewreset', this.draw, this); 222 | }, 223 | 224 | 225 | /** 226 | * [getMapPixelBounds description] 227 | * @return {type} [description] 228 | */ 229 | getMapPixelBounds : function(){ 230 | var bounds = this.map.getPixelBounds(); 231 | return { max : { x : bounds.max.x, y : bounds.max.y }, min : { x : bounds.min.x, y : bounds.min.y } }; 232 | }, 233 | 234 | /** 235 | * [clearLayers Remove all child nodes of this layer from the DOM and initializes the layer.] 236 | */ 237 | clearLayers: function(){ 238 | this.multiPolygons = undefined; 239 | $('#poi-canvas'+ $(this.map._container).attr("id")).empty(); 240 | }, 241 | 242 | 243 | getRingSizesByZoom: function(icon, zoomLevel){ 244 | 245 | var ringSizes = {}; 246 | 247 | // console.log(icon); 248 | 249 | icon.sizes.forEach(function(size) { 250 | if ( zoomLevel <= size.toZoom && zoomLevel >= size.fromZoom ) { 251 | ringSizes.outerRing = size.outerRing; 252 | ringSizes.innerRing = size.innerRing; 253 | } 254 | }); 255 | 256 | return ringSizes; 257 | }, 258 | 259 | drawRingIcon: function(ctx, icon, pixel){ 260 | 261 | var ringSizes = this.getRingSizesByZoom(icon, this.map._zoom); 262 | 263 | ctx.beginPath(); 264 | ctx.arc(pixel.x, pixel.y, ringSizes.outerRing, 0, 2 * Math.PI, false); 265 | ctx.fillStyle = icon.strokeStyle; 266 | ctx.fill(); 267 | 268 | ctx.beginPath(); 269 | ctx.arc(pixel.x, pixel.y, ringSizes.innerRing, 0, 2 * Math.PI, false); 270 | ctx.fillStyle = icon.fillStyle; 271 | ctx.fill(); 272 | }, 273 | 274 | 275 | drawMarkerHover: function(item){ 276 | var c = document.getElementById(this.poiMarkerHoverId); 277 | var ctx = c.getContext("2d"); 278 | ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); 279 | this.drawRingIcon(ctx, item.hoverIcon, item.currentPixel); 280 | }, 281 | 282 | drawMarkerClick: function(item){ 283 | var c = document.getElementById(this.poiMarkerClickId); 284 | var ctx = c.getContext("2d"); 285 | ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); 286 | this.drawRingIcon(ctx, item.clickIcon, item.currentPixel); 287 | }, 288 | 289 | clearMarkerHover: function(){ 290 | var c = document.getElementById(this.poiMarkerHoverId); 291 | var ctx = c.getContext("2d"); 292 | ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); 293 | }, 294 | 295 | clearMarkerClick: function(){ 296 | var c = document.getElementById(this.poiMarkerClickId); 297 | var ctx = c.getContext("2d"); 298 | ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); 299 | }, 300 | 301 | 302 | getCanvas: function(width, height, zIndex, id) { 303 | var canvas = ''; 304 | return canvas; 305 | }, 306 | 307 | draw: function(item){ 308 | 309 | var point = r360.PolygonUtil.scale(item.coordinate, this.mapZoomPowerLookup[this.map._zoom]); 310 | var pixel = {x: parseInt(point.x - this.origin.x - this.offset.x), y : parseInt(point.y - this.origin.y - this.offset.y)}; 311 | 312 | item.currentPixel = pixel; 313 | 314 | if(this.containedByCanvas(pixel)){ 315 | if(this.arr[pixel.x + ";" + pixel.y] !== true){ 316 | this.drawRingIcon(this.mainMarcerCanvasCtx, item.icon, pixel); 317 | this.arr[pixel.x + ";" + pixel.y] = true; 318 | } 319 | } 320 | }, 321 | 322 | 323 | updateOffset: function(){ 324 | 325 | // calculate the offset in between map and svg in order to translate 326 | var canvasPosition = $('#canvas_div_' + this.id + '').offset(); 327 | this.mapPosition = $(this.map._container).offset(); 328 | 329 | if ( typeof this.offset == 'undefined' ) 330 | this.offset = { x : 0 , y : 0 }; 331 | 332 | // adjust the offset after map panning / zooming 333 | if ( canvasPosition ) { 334 | this.offset.x += (this.mapPosition.left - canvasPosition.left); 335 | this.offset.y += (this.mapPosition.top - canvasPosition.top); 336 | } 337 | }, 338 | 339 | 340 | resetCanvas: function(){ 341 | 342 | var that = this; 343 | 344 | this.canvasWidth = this.map.getSize().x; 345 | this.canvasHeight = this.map.getSize().y; 346 | 347 | this.updateOffset(); 348 | 349 | // clear layer from previous drawings 350 | $('#' + this.poiCanvasId).empty(); 351 | 352 | var translation = r360.Util.getTranslation(this.offset); 353 | 354 | var markerClickCanvas = this.getCanvas(this.canvasWidth, this.canvasHeight, 20, this.poiMarkerClickId); 355 | var markerHoverCanvas = this.getCanvas(this.canvasWidth, this.canvasHeight, 10, this.poiMarkerHoverId); 356 | var markerMainCanvas = this.getCanvas(this.canvasWidth, this.canvasHeight, 0, this.markerMainCanvasId); 357 | 358 | var canvas_div_id = "canvas_div_" + that.id; 359 | 360 | // add the canvas string to the container 361 | $('#'+ this.poiCanvasId).append( 362 | '
' + 363 | markerClickCanvas + markerHoverCanvas + markerMainCanvas + 364 | '
'); 365 | this.updateOffset(); 366 | 367 | c = document.getElementById(this.markerMainCanvasId); 368 | if (c !== null) 369 | this.mainMarcerCanvasCtx = c.getContext("2d"); 370 | 371 | this.arr = []; 372 | 373 | this.origin = this.map.getPixelOrigin(); 374 | }, 375 | 376 | 377 | /* 378 | * 379 | */ 380 | redraw: function(){ 381 | 382 | that = this; 383 | that.resetCanvas(); 384 | that.items.forEach(function(item){ 385 | that.draw(item); 386 | }); 387 | } 388 | }); 389 | } -------------------------------------------------------------------------------- /src/extension/leaflet/layer/LeafletPolygonLayer.js: -------------------------------------------------------------------------------- 1 | if ( typeof L === 'object' ) { 2 | /* 3 | * 4 | */ 5 | r360.LeafletPolygonLayer = L.Class.extend({ 6 | 7 | /** 8 | * This methods initializes the polygon layer's stroke width and polygon opacity. 9 | * It uses the default values, and in case the options contain other values, the 10 | * default values are overwritten. 11 | * 12 | * @method send 13 | * 14 | * @param {Object} [options] The typical JS options array. 15 | * @param {Number} [options.opacity] Defines the opacity of the polygons. 16 | * Higher values mean that the polygon is less opaque. 17 | * @param {Number} [options.strokeWidth] Defines the strokewidth of the polygons boundaries. 18 | * Since we have degenerated polygons (they can have no area), the stroke width defines the 19 | * thickness of a polygon. Thicker polygons are not as informative as thinner ones. 20 | */ 21 | initialize: function (options) { 22 | 23 | // set default parameters 24 | this.opacity = 0.4; 25 | this.strokeWidth = 30; 26 | this.tolerance = 15; 27 | this.extendWidthX = this.strokeWidth / 2; 28 | this.extendWidthY = this.strokeWidth / 2; 29 | this.backgroundColor = "#000000"; 30 | this.backgroundOpacity = 0.5; 31 | this.colors = [ 32 | { time : 300 , color : "#006837", opacity : 0.1 }, 33 | { time : 600 , color : "#39B54A", opacity : 0.2 }, 34 | { time : 900 , color : "#8CC63F", opacity : 0.3 }, 35 | { time : 1200 , color : "#F7931E", opacity : 0.4 }, 36 | { time : 1500 , color : "#F15A24", opacity : 0.5 }, 37 | { time : 1800 , color : "#C1272D", opacity : 1.0 } 38 | ]; 39 | 40 | // overwrite defaults with optional parameters 41 | if ( typeof options !== 'undefined' ) { 42 | 43 | if ( typeof options.colors !== 'undefined') this.colors = options.colors; 44 | if ( typeof options.opacity !== 'undefined') this.opacity = options.opacity; 45 | if ( typeof options.strokeWidth !== 'undefined') this.strokeWidth = options.strokeWidth; 46 | if ( typeof options.inverse !== 'undefined') this.inverse = options.inverse; 47 | if ( typeof options.tolerance !== 'undefined') this.tolerance = options.tolerance; 48 | if ( typeof options.extendWidthX !== 'undefined') this.extendWidthX = options.extendWidthX; 49 | if ( typeof options.extendWidthY !== 'undefined') this.extendWidthY = options.extendWidthY; 50 | } 51 | }, 52 | 53 | /** 54 | * [setInverse Sets this layer to the inverse representation, meaning only reachable parts are displayed 55 | * and the rest is greyed out.] 56 | * @param {type} inverse [true or false] 57 | */ 58 | setInverse: function(inverse){ 59 | this.inverse = inverse; 60 | }, 61 | 62 | /** 63 | * @return {type} [returns the current state of this layer] 64 | */ 65 | getInverse: function(){ 66 | return this.inverse; 67 | }, 68 | 69 | /** 70 | * [getBoundingBox3857 returns a boundingbox (in web mercator) from the left bottom to the top right of this layer] 71 | * @return {type} [description] 72 | */ 73 | getBoundingBox3857 : function(){ 74 | 75 | return this.multiPolygons[0] ? this.multiPolygons[0].getBoundingBox3857() : undefined; 76 | }, 77 | 78 | /** 79 | * [getBoundingBox4326 returns a boundingbox (in wgs84) from the left bottom to the top right of this layer] 80 | * @return {type} [description] 81 | */ 82 | getBoundingBox4326 : function(){ 83 | 84 | return this.multiPolygons[0] ? this.multiPolygons[0].getBoundingBox4326() : undefined; 85 | }, 86 | 87 | /* 88 | * 89 | */ 90 | onAdd: function (map) { 91 | 92 | this.map = map; 93 | 94 | this.id = r360.Util.generateId(); 95 | 96 | // create a DOM element with a unique ID to have multiple maps on one page 97 | this.element = L.DomUtil.create('div', 'r360-leaflet-polygon-layer-' + $(map._container).attr("id") + '-' + this.id + ' leaflet-zoom-hide'); 98 | $(this.element).attr("id", "canvas" + $(this.map._container).attr("id") + '-' + this.id); 99 | $(this.element).css("position", "inherit"); 100 | 101 | // we append the layer to the overlay pane at the last position 102 | this.map.getPanes().overlayPane.appendChild(this.element); 103 | 104 | // add a view redraw event listener for updating layer's position 105 | // zoom in/out, panning 106 | this.map.on('moveend', this.draw, this); 107 | 108 | // repaint layer 109 | this.draw(); 110 | }, 111 | 112 | /** 113 | * [fitMap adjust the map to fit the complete polygon with maximum zoom level] 114 | * @return {type} [description] 115 | */ 116 | fitMap: function(options){ 117 | 118 | // we have to transform the r360.latLngBounds to a L.latLngBounds since the map object 119 | // only knows the leaflet version 120 | var bounds = this.getBoundingBox4326(); 121 | if (typeof bounds === 'undefined') return; 122 | 123 | var sw = bounds.getSouthWest(), ne = bounds.getNorthEast(); 124 | 125 | this.map.fitBounds(L.latLngBounds(L.latLng({ lat : sw.lat, lng : sw.lng}), L.latLng({ lat : ne.lat, lng : ne.lng})), options); 126 | }, 127 | 128 | /** 129 | * [clearAndAddLayers clears the polygons from this layer and adds the new ones. If fitMap is not undefined wesvg 130 | * also adjust the map bounds/zoom to show the polygons as big as possible.] 131 | * @param {type} multiPolygons [description] 132 | * @return {type} [description] 133 | */ 134 | clearAndAddLayers : function(multiPolygons, fitMap, options){ 135 | 136 | this.clearLayers(); 137 | this.addLayer(multiPolygons); 138 | 139 | if ( typeof fitMap !== 'undefined' && fitMap === true ) this.fitMap(options); 140 | 141 | if ( options && options.callback && typeof options.callback === "function" ) { 142 | 143 | this.map.addOneTimeEventListener('moveend', function(){ 144 | 145 | options.callback(); 146 | }); 147 | } 148 | 149 | return this; 150 | }, 151 | 152 | /** 153 | * [addLayer description] 154 | * @param {type} multiPolygons [description] 155 | */ 156 | addLayer : function(multiPolygons) { 157 | 158 | this.multiPolygons = multiPolygons; 159 | 160 | // paint them 161 | this.draw(); 162 | }, 163 | 164 | /** 165 | * [addTo Adds this layer to the given map] 166 | * @param {type} map [the leaflet map on which the layer should be drawn] 167 | */ 168 | addTo: function (map) { 169 | 170 | map.addLayer(this); 171 | return this; 172 | }, 173 | 174 | /** 175 | * [onRemove description] 176 | * @param {type} map [description] 177 | * @return {type} [description] 178 | */ 179 | onRemove: function (map) { 180 | 181 | // remove layer's DOM elements and listeners 182 | map.getPanes().overlayPane.removeChild(this.element); 183 | map.off('viewreset', this.draw, this); 184 | }, 185 | 186 | /** 187 | * [createSvgData Creates the SVG representation of a given polygon] 188 | * @param {type} polygon [description] 189 | * @return {type} [description] 190 | */ 191 | createSvgData: function(polygon){ 192 | 193 | var bounds = r360.PolygonUtil.extendBounds(this.getMapPixelBounds(), this.extendWidthX, this.extendWidthY); 194 | return r360.SvgUtil.createSvgData(polygon, { 195 | bounds : bounds, 196 | scale : Math.pow(2, this.map._zoom) * 256, 197 | tolerance : this.tolerance, 198 | pixelOrigin : this.map.getPixelOrigin(), 199 | offset : this.offset 200 | }); 201 | }, 202 | 203 | /** 204 | * [getMapPixelBounds description] 205 | * @return {type} [description] 206 | */ 207 | getMapPixelBounds : function(){ 208 | 209 | var bounds = this.map.getPixelBounds(); 210 | return { max : { x : bounds.max.x, y : bounds.max.y }, min : { x : bounds.min.x, y : bounds.min.y } }; 211 | }, 212 | 213 | /** 214 | * [clearLayers Remove all child nodes of this layer from the DOM and initializes the layer.] 215 | */ 216 | clearLayers: function(){ 217 | 218 | this.multiPolygons = undefined; 219 | $('#canvas'+ $(this.map._container).attr("id") + '-' + this.id).empty(); 220 | }, 221 | 222 | setStrokeWidth: function(strokeWidth){ 223 | 224 | this.strokeWidth = strokeWidth; 225 | }, 226 | 227 | setColors: function(colors) { 228 | 229 | this.colors = colors; 230 | 231 | if ( typeof this.multiPolygons == 'undefined' ) return; 232 | 233 | for ( var i = 0 ; i < this.multiPolygons.length ; i++){ 234 | var multipolygon = this.multiPolygons[i]; 235 | this.colors.forEach(function(colorSet) { 236 | if (colorSet.time == multipolygon.getTravelTime()) { 237 | multipolygon.setColor(colorSet.color); 238 | multipolygon.setOpacity(colorSet.opacity); 239 | } 240 | }); 241 | } 242 | 243 | this.draw(); 244 | }, 245 | 246 | /* 247 | * 248 | */ 249 | draw: function () { 250 | 251 | if ( typeof this.multiPolygons !== 'undefined' ) { 252 | 253 | this.extendWidthX = this.map.getSize().x * 1.8 - this.map.getSize().x ; 254 | this.extendWidthY = this.map.getSize().y * 1.8 - this.map.getSize().y ; 255 | 256 | this.svgWidth = this.map.getSize().x + this.extendWidthX; 257 | this.svgHeight = this.map.getSize().y + this.extendWidthY; 258 | 259 | // calculate the offset in between map and svg in order to translate 260 | var svgPosition = $('#svg_'+ $(this.map._container).attr("id") + '-' + this.id).offset(); 261 | var mapPosition = $(this.map._container).offset(); 262 | 263 | if ( typeof this.offset == 'undefined' ) 264 | this.offset = { x : 0 , y : 0 }; 265 | 266 | // adjust the offset after map panning / zooming 267 | if ( svgPosition ) { 268 | this.offset.x += (mapPosition.left - svgPosition.left) - this.extendWidthX / 2; 269 | this.offset.y += (mapPosition.top - svgPosition.top) - this.extendWidthY / 2; 270 | } 271 | 272 | // clear layer from previous drawings 273 | $('#canvas'+ $(this.map._container).attr("id") + '-' + this.id).empty(); 274 | 275 | var gElements = []; 276 | 277 | // go through each multipolygon (represents each travel time) 278 | for ( var i = 0 ; i < this.multiPolygons.length ; i++){ 279 | 280 | var multiPolygon = this.multiPolygons[i], svgData = []; 281 | 282 | // add each polygon for the given travel time 283 | for ( var j = 0; j < multiPolygon.polygons.length; j++) 284 | svgData.push(this.createSvgData(multiPolygon.polygons[j])); 285 | 286 | if ( svgData.length !== 0 ) 287 | gElements.push(r360.SvgUtil.getGElement(svgData, { 288 | color : !this.inverse ? this.getColor(multiPolygon) : 'black', 289 | opacity : !this.inverse ? 1 : this.getOpacity(multiPolygon), 290 | strokeWidth : this.strokeWidth 291 | })); 292 | } 293 | 294 | var options = { 295 | id : $(this.map._container).attr("id") + '-' + this.id, 296 | offset : this.offset, 297 | svgHeight : this.svgHeight, 298 | svgWidth : this.svgWidth, 299 | backgroundColor : this.backgroundColor, 300 | backgroundOpacity : this.backgroundOpacity, 301 | opacity : this.opacity, 302 | strokeWidth : this.strokeWidth 303 | }; 304 | 305 | // add the svg string to the container 306 | $('#canvas'+ $(this.map._container).attr("id") + '-' + this.id).append(!this.inverse ? r360.SvgUtil.getNormalSvgElement(gElements, options) 307 | : r360.SvgUtil.getInverseSvgElement(gElements, options)); 308 | } 309 | }, 310 | 311 | getColor: function(multiPolygon) { 312 | 313 | var color = "#000000"; 314 | 315 | this.colors.forEach(function(colorSet) { 316 | if (colorSet.time == multiPolygon.getTravelTime() && r360.has(colorSet, 'color')) { 317 | color = colorSet.color; 318 | } 319 | }); 320 | 321 | return color; 322 | }, 323 | 324 | getOpacity: function(multiPolygon) { 325 | 326 | var opacity = 1.0; 327 | 328 | this.colors.forEach(function(colorSet) { 329 | if (colorSet.time == multiPolygon.getTravelTime() && r360.has(colorSet, 'opacity')) { 330 | opacity = colorSet.opacity; 331 | } 332 | }); 333 | 334 | return opacity; 335 | }, 336 | 337 | // fix for leaflet 1.0 338 | _layerAdd: function(options) { 339 | this.onAdd(options.target); 340 | } 341 | }); 342 | 343 | r360.leafletPolygonLayer = function (options) { 344 | return new r360.LeafletPolygonLayer(options); 345 | }; 346 | } -------------------------------------------------------------------------------- /src/extension/leaflet/layer/TileLayer.js: -------------------------------------------------------------------------------- 1 | if (typeof L === 'object') { 2 | 3 | /* 4 | * 5 | */ 6 | r360.Basemap = L.TileLayer.extend({ 7 | 8 | options: { 9 | minZoom: 2, 10 | maxZoom: 18, 11 | style: 'bright', 12 | attribution: '© Route360° © OpenMapTiles © OpenStreetMap contributors', 13 | apikey: null, 14 | }, 15 | 16 | initialize: function initialize(options) { 17 | if (!options.apikey) { 18 | throw new Error('apikey required to access Route360 basemaps'); 19 | } 20 | 21 | options.styleName = r360.basemapsLookup[options.style] ? r360.basemapsLookup[options.style] : 'osm-bright-gl-style' 22 | 23 | options = L.setOptions(this, options); 24 | 25 | var tileUrl = 'https://maps.route360.net/styles/{styleName}/rendered/{z}/{x}/{y}.png?key={apikey}'; 26 | 27 | L.TileLayer.prototype.initialize.call(this, tileUrl, options); 28 | } 29 | 30 | }); 31 | 32 | /** 33 | * [r360.basemap returns a tilelayer for one of the r360 basemap styles. ] 34 | * @param {options} L.TileLayer.options [accepts standard L.TileLayer options, with the addition of 'style' and 'apikey' keys] 35 | * @return {r360.Basemap} L.TileLayer [returns new L.TileLayer instance of Route360 basemap] 36 | */ 37 | r360.basemap = function (options) { 38 | return new r360.Basemap(options); 39 | }; 40 | 41 | /** 42 | * [r360.basemaps returns a object of tilelayers for the r360 basemap styles. ] 43 | * @param {apikey} String [accepts string of Route360 apikey] 44 | * @return {basemaps} Object [returns object of Route360 basemaps, ready to be fed to L.control.layers] 45 | */ 46 | r360.basemaps = function (apikey) { 47 | return Object.keys(r360.basemapsLookup).reduce(function (acc, cur, i) { 48 | acc[cur] = r360.basemap({ style: cur, apikey: apikey }); 49 | return acc; 50 | }, {}); 51 | }; 52 | 53 | } -------------------------------------------------------------------------------- /src/extension/leaflet/util/LeafletUtil.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.LeafletUtil = { 5 | 6 | /* 7 | * Convenients method to generate a Leaflet marker with the 8 | * specified marker color. For available colors look at 'dist/images' 9 | * 10 | * @method getMarker 11 | * @param {Object} [latlon] The coordinate 12 | * @param {Number} [latlon.lat] The latitude of the coordinate. 13 | * @param {Number} [latlon.lng] The longitude of the coordinate. 14 | * @param {Object} [options] The options for the marker 15 | * @param {Number} [options.color] The color for the marker icon. 16 | */ 17 | getMarker : function(latlng, options){ 18 | 19 | var color = r360.has(options, 'color') ? '-' + options.color : '-blue'; 20 | 21 | options.icon = L.icon({ 22 | iconSize : [25, 41], // size of the icon 23 | iconUrl : options.iconPath + 'marker-icon' + color + '.png', 24 | iconAnchor : [12, 41], // point of the icon which will correspond to marker's location 25 | 26 | shadowSize : [41, 41], // size of the shadow 27 | shadowUrl : options.iconPath + 'marker-shadow.png', 28 | shadowAnchor : [41 / 3, 41], // point of the shadow which will correspond to marker's location 29 | 30 | popupAnchor : [0, -35] // point from which the popup should open relative to the iconAnchor 31 | }); 32 | 33 | return L.marker(latlng, options); 34 | }, 35 | 36 | fadeIn : function(layer, route, drawingTime, fadingType, options, onClick){ 37 | 38 | if ( typeof drawingTime == 'undefined' ) drawingTime = 0; 39 | if ( typeof fadingType == 'undefined') fadingType = 'travelTime'; 40 | 41 | fadePathSegment(0); 42 | 43 | function fadePathSegment(z){ 44 | 45 | // calculate fading time for segment 46 | segment = route.routeSegments[z]; 47 | percent = fadingType == "travelTime" ? segment.getTravelTime() / route.getTravelTime() : segment.getDistance() / route.getDistance(); 48 | 49 | timeToDraw = percent * drawingTime; 50 | 51 | // transfer don't have a linestring, just a point 52 | if ( segment.getType() != "TRANSFER" ) { 53 | fader(segment, timeToDraw, options, z); 54 | } 55 | else { 56 | 57 | if ( typeof options === 'undefined' || options.paintTransfer || (typeof options !== 'undefined' && !r360.has(options, 'paintTransfer') )) 58 | addTransferSegment(segment, options); 59 | 60 | if(++z < route.routeSegments.length) 61 | fadePathSegment(z); 62 | } 63 | } 64 | 65 | function addTransferSegment(segment, options){ 66 | 67 | addCircularMarker(segment.points[0], segment, options); 68 | 69 | // if inter station transfer -> involves two stops -> we need a second circle 70 | if( segment.points.length > 1 && segment.points[0].lat != segment.points[1].lat && segment.points[0].lng != segment.points[1].lng ) 71 | addCircularMarker(segment.points[1], segment, options); 72 | } 73 | 74 | function addCircularMarker(latLng, segment, options) { 75 | var marker = L.circleMarker(latLng, { 76 | color: !r360.isUndefined(options) && r360.has(options, 'transferColor') ? options.transferColor : segment.getColor(), 77 | fillColor: !r360.isUndefined(options) && r360.has(options, 'transferHaloColor') ? options.transferHaloColor : typeof segment.getHaloColor() !== 'undefined' ? segment.getHaloColor() : '#9D9D9D', 78 | fillOpacity: !r360.isUndefined(options) && r360.has(options, 'transferFillOpacity')? options.transferFillOpacity : 1, 79 | opacity: !r360.isUndefined(options) && r360.has(options, 'transferOpacity') ? options.transferOpacity : 1, 80 | stroke: !r360.isUndefined(options) && r360.has(options, 'transferStroke') ? options.transferStroke : true, 81 | weight: !r360.isUndefined(options) && r360.has(options, 'transferWeight') ? options.transferWeight : 4, 82 | radius: !r360.isUndefined(options) && r360.has(options, 'transferRadius') ? options.transferRadius : 8 83 | }); 84 | 85 | var popup = !r360.isUndefined(options) && r360.has(options, 'popup') ? options.popup : "INSERT_TEXT"; 86 | 87 | if ( typeof segment !== 'undefined') { 88 | 89 | var variable = !r360.contains(['walk', 'transit', 'source', 'target', 'bike', 'car'], segment.startname) ? segment.startname : ''; 90 | variable = variable == '' && !r360.contains(['walk', 'transit', 'source', 'target', 'bike', 'car'], segment.endname) ? segment.endname : variable; 91 | 92 | popup = popup.replace('INSERT_TEXT', variable); 93 | } 94 | 95 | if ( !r360.isUndefined(options) && r360.has(options, 'popup') ) { 96 | 97 | marker.bindPopup(popup) 98 | marker.on('mouseover', function(){ marker.openPopup(); }) 99 | } 100 | 101 | marker.addTo(layer); 102 | marker.bringToFront(); 103 | } 104 | 105 | 106 | function fader(segment, millis, options, z){ 107 | 108 | var polylineOptions = {}; 109 | polylineOptions.color = !r360.isUndefined(options) && r360.has(options, 'color') ? options.color : segment.getColor(); 110 | polylineOptions.opacity = !r360.isUndefined(options) && r360.has(options, 'opacity' ) ? options.opacity : 0.8; 111 | polylineOptions.weight = !r360.isUndefined(options) && r360.has(options, 'weight' ) ? options.weight : 5; 112 | 113 | if ( segment.getType() != "TRANSIT" && (segment.getType() == "WALK") ) { 114 | 115 | polylineOptions.color = !r360.isUndefined(options) && r360.has(options, 'walkColor' ) ? options.walkColor : '#006F35'; 116 | polylineOptions.weight = !r360.isUndefined(options) && r360.has(options, 'walkWeight' ) ? options.walkWeight : 7; 117 | polylineOptions.dashArray = !r360.isUndefined(options) && r360.has(options, 'walkDashArray' ) ? options.walkDashArray : "1, 10"; 118 | } 119 | 120 | var polylineHaloOptions = {}; 121 | polylineHaloOptions.weight = !r360.isUndefined(options) && r360.has(options, 'haloWeight' ) ? options.haloWeight : 10; 122 | polylineHaloOptions.opacity = !r360.isUndefined(options) && r360.has(options, 'haloOpacity' ) ? options.haloOpacity : 0.7; 123 | polylineHaloOptions.color = !r360.isUndefined(options) && r360.has(options, 'haloColor') ? options.haloColor : typeof segment.getHaloColor() !== 'undefined' ? segment.getHaloColor() : '#9D9D9D'; 124 | 125 | // 15ms for one peace. So if we want do draw the segment in 1 sec we need 66 pieces 126 | var pieces = millis / 15; 127 | var choppedLine = chopLineString(segment.getPoints(), pieces); 128 | var haloLine = L.polyline(choppedLine[0], polylineHaloOptions).addTo(layer); 129 | var polyLine = L.polyline(choppedLine[0], polylineOptions).addTo(layer); 130 | 131 | // add event listener 132 | haloLine.on('click', onClick); 133 | polyLine.on('click', onClick); 134 | 135 | fadeLine(polyLine, haloLine, choppedLine, 1, z) 136 | }; 137 | 138 | /* 139 | function is recalling itself every 25ms 140 | if you want the line to be drawn in one second you need to add a chopped line in (roughly) 40 pieces 141 | When line is drawn fadePathSegment is called in order to draw the next segment. 142 | */ 143 | 144 | function fadeLine(polyLine, haloLine, choppedLine, i, z){ 145 | 146 | var latlngs = polyLine.getLatLngs(); 147 | 148 | for ( var j = 0 ; j < choppedLine[i].length ; j++ ) 149 | latlngs.push(choppedLine[i][j]) 150 | 151 | 152 | if ( latlngs.length != 0 ) { 153 | haloLine.setLatLngs(latlngs); 154 | polyLine.setLatLngs(latlngs); 155 | } 156 | 157 | if ( ++i < choppedLine.length ) { 158 | setTimeout(function(){ 159 | fadeLine(polyLine, haloLine, choppedLine, i, z); 160 | }, 15); 161 | }else{ 162 | if(++z < route.routeSegments.length) 163 | fadePathSegment(z); 164 | } 165 | } 166 | 167 | /* 168 | chops a linestring in a chosen number of equal pieces 169 | */ 170 | 171 | function chopLineString(latlngs, pieces){ 172 | 173 | var length = 0; 174 | var steps = 1 / pieces; 175 | var percentSoFar = 0; 176 | var segmentDistance; 177 | var segmentPercent; 178 | var newLatLngs = new Array(); 179 | 180 | for(var i = 1; i < latlngs.length; i++){ 181 | length += latlngs[i-1].distanceTo(latlngs[i]); 182 | } 183 | 184 | var part = new Array(); 185 | 186 | for(var i = 0; i < latlngs.length -1; i++){ 187 | 188 | 189 | part.push(latlngs[i]); 190 | 191 | segmentDistance = latlngs[i].distanceTo(latlngs[i + 1]); 192 | segmentPercent = segmentDistance / length; 193 | percentSoFar += segmentPercent; 194 | 195 | if(percentSoFar >= steps){ 196 | while(percentSoFar >= steps){ 197 | percent = ((steps - (percentSoFar - segmentPercent))/segmentPercent); 198 | part.push(interpolatePoint(latlngs[i],latlngs[i + 1],percent)); 199 | steps += 1 / pieces; 200 | 201 | newLatLngs.push(part); 202 | part = new Array(); 203 | } 204 | } 205 | } 206 | 207 | newLatLngs.push(part); 208 | part = new Array(); 209 | part.push(latlngs[latlngs.length -1]); 210 | newLatLngs.push(part); 211 | return newLatLngs; 212 | }; 213 | 214 | function interpolatePoint(latlng1, latlng2, percent){ 215 | 216 | var tempmap; 217 | 218 | /* 219 | ugly hack. shall be redone when working with projected coordinates 220 | */ 221 | if(typeof layer.project != "undefined"){ 222 | tempmap = layer; 223 | }else{ 224 | tempmap = layer._map; 225 | } 226 | var p1 = tempmap.project(latlng1); 227 | var p2 = tempmap.project(latlng2); 228 | 229 | var xNew = (p2.x - p1.x) * percent + p1.x; 230 | var yNew = (p2.y - p1.y) * percent + p1.y; 231 | var newPoint = new r360.point(xNew, yNew); 232 | 233 | var latlng = tempmap.unproject(L.point(newPoint.x, newPoint.y)); 234 | 235 | return latlng; 236 | }; 237 | } 238 | }; -------------------------------------------------------------------------------- /src/geometry/crs/CRS.EPSG3857.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping and is used by Leaflet by default. 3 | */ 4 | 5 | r360.CRS.EPSG3857 = r360.extend({}, r360.CRS.Earth, { 6 | code: 'EPSG:3857', 7 | projection: r360.Projection.SphericalMercator, 8 | 9 | transformation: (function () { 10 | var scale = 0.5 / (Math.PI); 11 | return new r360.Transformation(scale, 0.5, -scale, 0.5); 12 | }()) 13 | }); 14 | 15 | r360.CRS.EPSG900913 = r360.extend({}, r360.CRS.EPSG3857, { 16 | code: 'EPSG:900913' 17 | }); 18 | -------------------------------------------------------------------------------- /src/geometry/crs/CRS.Earth.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.CRS.Earth is the base class for all CRS representing Earth. 3 | */ 4 | 5 | r360.CRS.Earth = r360.extend({}, r360.CRS, { 6 | wrapLng: [-180, 180], 7 | 8 | R: 6378137, 9 | 10 | // distance between two geographical points using spherical law of cosines approximation 11 | distance: function (latlng1, latlng2) { 12 | var rad = Math.PI / 180, 13 | lat1 = latlng1.lat * rad, 14 | lat2 = latlng2.lat * rad, 15 | a = Math.sin(lat1) * Math.sin(lat2) + 16 | Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad); 17 | 18 | return this.R * Math.acos(Math.min(a, 1)); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /src/geometry/crs/CRS.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.CRS is the base object for all defined CRS (Coordinate Reference Systems) in Leaflet. 3 | */ 4 | 5 | r360.CRS = { 6 | // converts geo coords to pixel ones 7 | latLngToPoint: function (latlng, zoom) { 8 | var projectedPoint = this.projection.project(latlng), 9 | scale = this.scale(zoom); 10 | 11 | return this.transformation._transform(projectedPoint, scale); 12 | }, 13 | 14 | // converts pixel coords to geo coords 15 | pointToLatLng: function (point, zoom) { 16 | var scale = this.scale(zoom), 17 | untransformedPoint = this.transformation.untransform(point, scale); 18 | 19 | return this.projection.unproject(untransformedPoint); 20 | }, 21 | 22 | // converts geo coords to projection-specific coords (e.g. in meters) 23 | project: function (latlng) { 24 | return this.projection.project(latlng); 25 | }, 26 | 27 | // converts projected coords to geo coords 28 | unproject: function (point) { 29 | return this.projection.unproject(point); 30 | }, 31 | 32 | // defines how the world scales with zoom 33 | scale: function (zoom) { 34 | return 256 * Math.pow(2, zoom); 35 | }, 36 | 37 | // returns the bounds of the world in projected coords if applicable 38 | getProjectedBounds: function (zoom) { 39 | if (this.infinite) { return null; } 40 | 41 | var b = this.projection.bounds, 42 | s = this.scale(zoom), 43 | min = this.transformation.transform(b.min, s), 44 | max = this.transformation.transform(b.max, s); 45 | 46 | return r360.bounds(min, max); 47 | }, 48 | 49 | // whether a coordinate axis wraps in a given range (e.g. longitude from -180 to 180); depends on CRS 50 | // wrapLng: [min, max], 51 | // wrapLat: [min, max], 52 | 53 | // if true, the coordinate space will be unbounded (infinite in all directions) 54 | // infinite: false, 55 | 56 | // wraps geo coords in certain ranges if applicable 57 | wrapLatLng: function (latlng) { 58 | var lng = this.wrapLng ? r360.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, 59 | lat = this.wrapLat ? r360.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, 60 | alt = latlng.alt; 61 | 62 | return r360.latLng(lat, lng, alt); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /src/geometry/projection/Projection.SphericalMercator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. 3 | */ 4 | r360.Projection = {}; 5 | 6 | r360.Projection.SphericalMercator = { 7 | 8 | R: 6378137, 9 | 10 | project: function (latlng) { 11 | var d = Math.PI / 180, 12 | max = 1 - 1E-15, 13 | sin = Math.max(Math.min(Math.sin(latlng.lat * d), max), -max); 14 | 15 | return new r360.Point( 16 | this.R * latlng.lng * d, 17 | this.R * Math.log((1 + sin) / (1 - sin)) / 2); 18 | }, 19 | 20 | unproject: function (point) { 21 | var d = 180 / Math.PI; 22 | 23 | return new r360.LatLng( 24 | (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, 25 | point.x * d / this.R); 26 | }, 27 | 28 | bounds: (function () { 29 | var d = 6378137 * Math.PI; 30 | return r360.bounds([-d, -d], [d, d]); 31 | })() 32 | }; 33 | -------------------------------------------------------------------------------- /src/geometry/transformation/Transformation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.Transformation is an utility class to perform simple point transformations through a 2d-matrix. 3 | */ 4 | 5 | r360.Transformation = function (a, b, c, d) { 6 | this._a = a; 7 | this._b = b; 8 | this._c = c; 9 | this._d = d; 10 | }; 11 | 12 | r360.Transformation.prototype = { 13 | transform: function (point, scale) { // (Point, Number) -> Point 14 | return this._transform(point.clone(), scale); 15 | }, 16 | 17 | // destructive transform (faster) 18 | _transform: function (point, scale) { 19 | 20 | scale = scale || 1; 21 | point.x = scale * (this._a * point.x + this._b); 22 | point.y = scale * (this._c * point.y + this._d); 23 | return point; 24 | }, 25 | 26 | untransform: function (point, scale) { 27 | scale = scale || 1; 28 | return new r360.Point( 29 | (point.x / scale - this._b) / this._a, 30 | (point.y / scale - this._d) / this._c); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/geometry/types/bounds/Bounds.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.Bounds represents a rectangular area on the screen in pixel coordinates. 3 | */ 4 | 5 | r360.Bounds = function (a, b) { //(Point, Point) or Point[] 6 | if (!a) { return; } 7 | 8 | var points = b ? [a, b] : a; 9 | 10 | for (var i = 0, len = points.length; i < len; i++) { 11 | this.extend(points[i]); 12 | } 13 | }; 14 | 15 | r360.Bounds.prototype = { 16 | // extend the bounds to contain the given point 17 | extend: function (point) { // (Point) 18 | point = r360.point(point); 19 | 20 | if (!this.min && !this.max) { 21 | this.min = point.clone(); 22 | this.max = point.clone(); 23 | } else { 24 | this.min.x = Math.min(point.x, this.min.x); 25 | this.max.x = Math.max(point.x, this.max.x); 26 | this.min.y = Math.min(point.y, this.min.y); 27 | this.max.y = Math.max(point.y, this.max.y); 28 | } 29 | return this; 30 | }, 31 | 32 | getCenter: function (round) { // (Boolean) -> Point 33 | return new r360.Point( 34 | (this.min.x + this.max.x) / 2, 35 | (this.min.y + this.max.y) / 2, round); 36 | }, 37 | 38 | getBottomLeft: function () { // -> Point 39 | return new r360.Point(this.min.x, this.max.y); 40 | }, 41 | 42 | getTopRight: function () { // -> Point 43 | return new r360.Point(this.max.x, this.min.y); 44 | }, 45 | 46 | getSize: function () { 47 | return this.max.subtract(this.min); 48 | }, 49 | 50 | contains: function (obj) { // (Bounds) or (Point) -> Boolean 51 | var min, max; 52 | 53 | if (typeof obj[0] === 'number' || obj instanceof r360.Point) { 54 | obj = r360.point(obj); 55 | } else { 56 | obj = r360.bounds(obj); 57 | } 58 | 59 | if (obj instanceof r360.Bounds) { 60 | min = obj.min; 61 | max = obj.max; 62 | } else { 63 | min = max = obj; 64 | } 65 | 66 | return (min.x >= this.min.x) && 67 | (max.x <= this.max.x) && 68 | (min.y >= this.min.y) && 69 | (max.y <= this.max.y); 70 | }, 71 | 72 | intersects: function (bounds) { // (Bounds) -> Boolean 73 | bounds = r360.bounds(bounds); 74 | 75 | var min = this.min, 76 | max = this.max, 77 | min2 = bounds.min, 78 | max2 = bounds.max, 79 | xIntersects = (max2.x >= min.x) && (min2.x <= max.x), 80 | yIntersects = (max2.y >= min.y) && (min2.y <= max.y); 81 | 82 | return xIntersects && yIntersects; 83 | }, 84 | 85 | overlaps: function (bounds) { // (Bounds) -> Boolean 86 | bounds = r360.bounds(bounds); 87 | 88 | var min = this.min, 89 | max = this.max, 90 | min2 = bounds.min, 91 | max2 = bounds.max, 92 | xOverlaps = (max2.x > min.x) && (min2.x < max.x), 93 | yOverlaps = (max2.y > min.y) && (min2.y < max.y); 94 | 95 | return xOverlaps && yOverlaps; 96 | }, 97 | 98 | isValid: function () { 99 | return !!(this.min && this.max); 100 | } 101 | }; 102 | 103 | r360.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) 104 | if (!a || a instanceof r360.Bounds) { 105 | return a; 106 | } 107 | return new r360.Bounds(a, b); 108 | }; 109 | -------------------------------------------------------------------------------- /src/geometry/types/bounds/LatLngBounds.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.LatLngBounds represents a rectangular area on the map in geographical coordinates. 3 | */ 4 | 5 | r360.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) 6 | if (!southWest) { return; } 7 | 8 | var latlngs = northEast ? [southWest, northEast] : southWest; 9 | 10 | for (var i = 0, len = latlngs.length; i < len; i++) { 11 | this.extend(latlngs[i]); 12 | } 13 | }; 14 | 15 | r360.LatLngBounds.prototype = { 16 | 17 | // extend the bounds to contain the given point or bounds 18 | extend: function (obj) { // (LatLng) or (LatLngBounds) 19 | var sw = this._southWest, 20 | ne = this._northEast, 21 | sw2, ne2; 22 | 23 | if (obj instanceof r360.LatLng) { 24 | sw2 = obj; 25 | ne2 = obj; 26 | 27 | } else if (obj instanceof r360.LatLngBounds) { 28 | sw2 = obj._southWest; 29 | ne2 = obj._northEast; 30 | 31 | if (!sw2 || !ne2) { return this; } 32 | 33 | } else { 34 | return obj ? this.extend(r360.latLng(obj) || r360.latLngBounds(obj)) : this; 35 | } 36 | 37 | if (!sw && !ne) { 38 | this._southWest = new r360.LatLng(sw2.lat, sw2.lng); 39 | this._northEast = new r360.LatLng(ne2.lat, ne2.lng); 40 | } else { 41 | sw.lat = Math.min(sw2.lat, sw.lat); 42 | sw.lng = Math.min(sw2.lng, sw.lng); 43 | ne.lat = Math.max(ne2.lat, ne.lat); 44 | ne.lng = Math.max(ne2.lng, ne.lng); 45 | } 46 | 47 | return this; 48 | }, 49 | 50 | // extend the bounds by a percentage 51 | pad: function (bufferRatio) { // (Number) -> LatLngBounds 52 | var sw = this._southWest, 53 | ne = this._northEast, 54 | heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, 55 | widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 56 | 57 | return new r360.LatLngBounds( 58 | new r360.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 59 | new r360.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); 60 | }, 61 | 62 | getCenter: function () { // -> LatLng 63 | return new r360.LatLng( 64 | (this._southWest.lat + this._northEast.lat) / 2, 65 | (this._southWest.lng + this._northEast.lng) / 2); 66 | }, 67 | 68 | getSouthWest: function () { 69 | return this._southWest; 70 | }, 71 | 72 | getNorthEast: function () { 73 | return this._northEast; 74 | }, 75 | 76 | getNorthWest: function () { 77 | return new r360.LatLng(this.getNorth(), this.getWest()); 78 | }, 79 | 80 | getSouthEast: function () { 81 | return new r360.LatLng(this.getSouth(), this.getEast()); 82 | }, 83 | 84 | getWest: function () { 85 | return this._southWest.lng; 86 | }, 87 | 88 | getSouth: function () { 89 | return this._southWest.lat; 90 | }, 91 | 92 | getEast: function () { 93 | return this._northEast.lng; 94 | }, 95 | 96 | getNorth: function () { 97 | return this._northEast.lat; 98 | }, 99 | 100 | contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean 101 | if (typeof obj[0] === 'number' || obj instanceof r360.LatLng) { 102 | obj = r360.latLng(obj); 103 | } else { 104 | obj = r360.latLngBounds(obj); 105 | } 106 | 107 | var sw = this._southWest, 108 | ne = this._northEast, 109 | sw2, ne2; 110 | 111 | if (obj instanceof r360.LatLngBounds) { 112 | sw2 = obj.getSouthWest(); 113 | ne2 = obj.getNorthEast(); 114 | } else { 115 | sw2 = ne2 = obj; 116 | } 117 | 118 | return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && 119 | (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); 120 | }, 121 | 122 | intersects: function (bounds) { // (LatLngBounds) -> Boolean 123 | bounds = r360.latLngBounds(bounds); 124 | 125 | var sw = this._southWest, 126 | ne = this._northEast, 127 | sw2 = bounds.getSouthWest(), 128 | ne2 = bounds.getNorthEast(), 129 | 130 | latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), 131 | lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); 132 | 133 | return latIntersects && lngIntersects; 134 | }, 135 | 136 | overlaps: function (bounds) { // (LatLngBounds) -> Boolean 137 | bounds = r360.latLngBounds(bounds); 138 | 139 | var sw = this._southWest, 140 | ne = this._northEast, 141 | sw2 = bounds.getSouthWest(), 142 | ne2 = bounds.getNorthEast(), 143 | 144 | latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), 145 | lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); 146 | 147 | return latOverlaps && lngOverlaps; 148 | }, 149 | 150 | toBBoxString: function () { 151 | return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); 152 | }, 153 | 154 | equals: function (bounds) { // (LatLngBounds) 155 | if (!bounds) { return false; } 156 | 157 | bounds = r360.latLngBounds(bounds); 158 | 159 | return this._southWest.equals(bounds.getSouthWest()) && 160 | this._northEast.equals(bounds.getNorthEast()); 161 | }, 162 | 163 | isValid: function () { 164 | return !!(this._southWest && this._northEast); 165 | } 166 | }; 167 | 168 | //TODO International date line? 169 | 170 | r360.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) 171 | if (!a || a instanceof r360.LatLngBounds) { 172 | return a; 173 | } 174 | return new r360.LatLngBounds(a, b); 175 | }; 176 | -------------------------------------------------------------------------------- /src/geometry/types/linestring/LineString.js: -------------------------------------------------------------------------------- 1 | r360.lineString = function (coordinateArray) { 2 | return new r360.LineString(coordinateArray); 3 | }; 4 | 5 | r360.LineString = function(coordinateArray) { 6 | 7 | // default min/max values 8 | this.topRight_3857 = new r360.Point(-20026377, -20048967); 9 | this.bottomLeft_3857 = new r360.Point(+20026377, +20048967); 10 | 11 | // coordinates in leaflets own system 12 | this.coordinates = []; 13 | 14 | for ( var i = coordinateArray.length -1 ; i >= 0 ; i--) { 15 | if ( coordinateArray[i].x > this.topRight_3857.x) this.topRight_3857.x = coordinateArray[i].x; 16 | if ( coordinateArray[i].y > this.topRight_3857.y) this.topRight_3857.y = coordinateArray[i].y; 17 | if ( coordinateArray[i].x < this.bottomLeft_3857.x) this.bottomLeft_3857.x = coordinateArray[i].x; 18 | if ( coordinateArray[i].y < this.bottomLeft_3857.y) this.bottomLeft_3857.y = coordinateArray[i].y; 19 | } 20 | 21 | // TODO refactore, this can be done in a single iteration of the array 22 | for ( var i = 0; i < coordinateArray.length; i++ ) { 23 | this.coordinates.push(r360.Util.webMercatorToLeaflet(coordinateArray[i])); 24 | } 25 | 26 | /** 27 | * [getTopRight4326 description] 28 | * @return {type} [description] 29 | */ 30 | this.getTopRight4326 = function(){ 31 | return r360.Util.webMercatorToLatLng(new r360.Point(this.topRight_3857.x, this.topRight_3857.y)); 32 | } 33 | 34 | /** 35 | * [getTopRight3857 description] 36 | * @return {type} [description] 37 | */ 38 | this.getTopRight3857 = function(){ 39 | return this.topRight_3857; 40 | } 41 | 42 | /** 43 | * [getTopRightDecimal description] 44 | * @return {type} [description] 45 | */ 46 | this.getTopRightDecimal = function(){ 47 | return r360.Util.webMercatorToLeaflet(this.topRight_3857); 48 | } 49 | 50 | /** 51 | * [getBottomLeft4326 description] 52 | * @return {type} [description] 53 | */ 54 | this.getBottomLeft4326 = function(){ 55 | return r360.Util.webMercatorToLatLng(new r360.Point(this.bottomLeft_3857.x, this.bottomLeft_3857.y)); 56 | } 57 | 58 | /** 59 | * [getBottomLeft3857 description] 60 | * @return {type} [description] 61 | */ 62 | this.getBottomLeft3857 = function(){ 63 | return this.bottomLeft_3857; 64 | } 65 | 66 | /** 67 | * [getBottomLeftDecimal description] 68 | * @return {type} [description] 69 | */ 70 | this.getBottomLeftDecimal = function(){ 71 | return r360.Util.webMercatorToLeaflet(this.bottomLeft_3857); 72 | } 73 | 74 | /** 75 | * [getCoordinates description] 76 | * @return {type} [description] 77 | */ 78 | this.getCoordinates = function(){ 79 | return this.coordinates; 80 | } 81 | 82 | /** 83 | * [getCoordinate description] 84 | * @param {type} index [description] 85 | * @return {type} [description] 86 | */ 87 | this.getCoordinate = function(index){ 88 | return this.coordinates[index]; 89 | } 90 | } -------------------------------------------------------------------------------- /src/geometry/types/point/LatLng.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.LatLng represents a geographical point with latitude and longitude coordinates. 3 | */ 4 | 5 | r360.LatLng = function (lat, lng, alt) { 6 | if (isNaN(lat) || isNaN(lng)) { 7 | throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); 8 | } 9 | 10 | this.lat = +lat; 11 | this.lng = +lng; 12 | 13 | if (alt !== undefined) { 14 | this.alt = +alt; 15 | } 16 | }; 17 | 18 | r360.LatLng.prototype = { 19 | equals: function (obj, maxMargin) { 20 | if (!obj) { return false; } 21 | 22 | obj = r360.latLng(obj); 23 | 24 | var margin = Math.max( 25 | Math.abs(this.lat - obj.lat), 26 | Math.abs(this.lng - obj.lng)); 27 | 28 | return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); 29 | }, 30 | 31 | toString: function (precision) { 32 | return 'LatLng(' + 33 | r360.Util.formatNum(this.lat, precision) + ', ' + 34 | r360.Util.formatNum(this.lng, precision) + ')'; 35 | }, 36 | 37 | distanceTo: function (other) { 38 | return r360.CRS.Earth.distance(this, r360.latLng(other)); 39 | }, 40 | 41 | wrap: function () { 42 | return r360.CRS.Earth.wrapLatLng(this); 43 | }, 44 | 45 | toBounds: function (sizeInMeters) { 46 | var latAccuracy = 180 * sizeInMeters / 40075017, 47 | lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); 48 | 49 | return r360.latLngBounds( 50 | [this.lat - latAccuracy, this.lng - lngAccuracy], 51 | [this.lat + latAccuracy, this.lng + lngAccuracy]); 52 | }, 53 | 54 | clone: function () { 55 | return new r360.LatLng(this.lat, this.lng, this.alt); 56 | } 57 | }; 58 | 59 | 60 | // constructs LatLng with different signatures 61 | // (LatLng) or ([Number, Number]) or (Number, Number) or (Object) 62 | 63 | r360.latLng = function (a, b, c) { 64 | if (a instanceof r360.LatLng) { 65 | return a; 66 | } 67 | if (r360.Util.isArray(a) && typeof a[0] !== 'object') { 68 | if (a.length === 3) { 69 | return new r360.LatLng(a[0], a[1], a[2]); 70 | } 71 | if (a.length === 2) { 72 | return new r360.LatLng(a[0], a[1]); 73 | } 74 | return null; 75 | } 76 | if (a === undefined || a === null) { 77 | return a; 78 | } 79 | if (typeof a === 'object' && 'lat' in a) { 80 | return new r360.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); 81 | } 82 | if (b === undefined) { 83 | return null; 84 | } 85 | return new r360.LatLng(a, b, c); 86 | }; 87 | -------------------------------------------------------------------------------- /src/geometry/types/point/Point.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.Point represents a point with x and y coordinates. 3 | */ 4 | 5 | r360.Point = function (x, y, round) { 6 | this.x = (round ? Math.round(x) : x); 7 | this.y = (round ? Math.round(y) : y); 8 | }; 9 | 10 | r360.Point.prototype = { 11 | 12 | clone: function () { 13 | return new r360.Point(this.x, this.y); 14 | }, 15 | 16 | // non-destructive, returns a new point 17 | add: function (point) { 18 | return this.clone()._add(r360.point(point)); 19 | }, 20 | 21 | // destructive, used directly for performance in situations where it's safe to modify existing point 22 | _add: function (point) { 23 | this.x += point.x; 24 | this.y += point.y; 25 | return this; 26 | }, 27 | 28 | subtract: function (point) { 29 | return this.clone()._subtract(r360.point(point)); 30 | }, 31 | 32 | _subtract: function (point) { 33 | this.x -= point.x; 34 | this.y -= point.y; 35 | return this; 36 | }, 37 | 38 | divideBy: function (num) { 39 | return this.clone()._divideBy(num); 40 | }, 41 | 42 | _divideBy: function (num) { 43 | this.x /= num; 44 | this.y /= num; 45 | return this; 46 | }, 47 | 48 | multiplyBy: function (num) { 49 | return this.clone()._multiplyBy(num); 50 | }, 51 | 52 | _multiplyBy: function (num) { 53 | this.x *= num; 54 | this.y *= num; 55 | return this; 56 | }, 57 | 58 | round: function () { 59 | return this.clone()._round(); 60 | }, 61 | 62 | _round: function () { 63 | this.x = Math.round(this.x); 64 | this.y = Math.round(this.y); 65 | return this; 66 | }, 67 | 68 | floor: function () { 69 | return this.clone()._floor(); 70 | }, 71 | 72 | _floor: function () { 73 | this.x = Math.floor(this.x); 74 | this.y = Math.floor(this.y); 75 | return this; 76 | }, 77 | 78 | ceil: function () { 79 | return this.clone()._ceil(); 80 | }, 81 | 82 | _ceil: function () { 83 | this.x = Math.ceil(this.x); 84 | this.y = Math.ceil(this.y); 85 | return this; 86 | }, 87 | 88 | distanceTo: function (point) { 89 | point = r360.point(point); 90 | 91 | var x = point.x - this.x, 92 | y = point.y - this.y; 93 | 94 | return Math.sqrt(x * x + y * y); 95 | }, 96 | 97 | equals: function (point) { 98 | point = r360.point(point); 99 | 100 | return point.x === this.x && 101 | point.y === this.y; 102 | }, 103 | 104 | contains: function (point) { 105 | point = r360.point(point); 106 | 107 | return Math.abs(point.x) <= Math.abs(this.x) && 108 | Math.abs(point.y) <= Math.abs(this.y); 109 | }, 110 | 111 | toString: function () { 112 | return 'Point(' + 113 | r360.Util.formatNum(this.x) + ', ' + 114 | r360.Util.formatNum(this.y) + ')'; 115 | } 116 | }; 117 | 118 | r360.point = function (x, y, round) { 119 | if (x instanceof r360.Point) { 120 | return x; 121 | } 122 | if (r360.Util.isArray(x)) { 123 | return new r360.Point(x[0], x[1]); 124 | } 125 | if (x === undefined || x === null) { 126 | return x; 127 | } 128 | return new r360.Point(x, y, round); 129 | }; 130 | -------------------------------------------------------------------------------- /src/geometry/types/polygon/MultiPolygon.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.MultiPolygon = function() { 5 | 6 | this.travelTime; 7 | this.color; 8 | this.polygons = new Array(); 9 | 10 | // default min/max values 11 | this.topRight_3857 = new r360.Point(-20026377, -20048967); 12 | this.bottomLeft_3857 = new r360.Point(+20026377, +20048967); 13 | 14 | /** 15 | * [addPolygon description] 16 | * @param {type} polygon [description] 17 | */ 18 | this.addPolygon = function(polygon){ 19 | this.polygons.push(polygon); 20 | 21 | if ( polygon.getOuterBoundary().getTopRight3857().x > this.topRight_3857.x) this.topRight_3857.x = polygon.getOuterBoundary().getTopRight3857().x; 22 | if ( polygon.getOuterBoundary().getTopRight3857().y > this.topRight_3857.y) this.topRight_3857.y = polygon.getOuterBoundary().getTopRight3857().y; 23 | if ( polygon.getOuterBoundary().getBottomLeft3857().x < this.bottomLeft_3857.x) this.bottomLeft_3857.x = polygon.getOuterBoundary().getBottomLeft3857().x; 24 | if ( polygon.getOuterBoundary().getBottomLeft3857().y < this.bottomLeft_3857.y) this.bottomLeft_3857.y = polygon.getOuterBoundary().getBottomLeft3857().y; 25 | } 26 | 27 | /** 28 | * [getBoundingBox3857 description] 29 | * @return {type} [description] 30 | */ 31 | this.getBoundingBox3857 = function() { 32 | 33 | return r360.bounds(this.bottomLeft_3857, this.topRight_3857); 34 | } 35 | 36 | /** 37 | * [getBoundingBox4326 description] 38 | * @return {type} [description] 39 | */ 40 | this.getBoundingBox4326 = function() { 41 | 42 | return r360.latLngBounds(r360.Util.webMercatorToLatLng(this.bottomLeft_3857), r360.Util.webMercatorToLatLng(this.topRight_3857)); 43 | } 44 | 45 | /** 46 | * [setOpacity description] 47 | * @param {type} opacity [description] 48 | */ 49 | this.setOpacity = function(opacity){ 50 | this.opacity = opacity; 51 | } 52 | 53 | /** 54 | * [getOpacity description] 55 | * @return {type} [description] 56 | */ 57 | this.getOpacity = function(){ 58 | return this.opacity; 59 | } 60 | 61 | /** 62 | * [getArea description] 63 | * @return {type} [description] 64 | */ 65 | this.getArea = function(){ 66 | 67 | var area = 0; 68 | this.polygons.forEach(function(polygon){ area += polygon.getArea(); }); 69 | return area; 70 | } 71 | 72 | /** 73 | * [getPolygons description] 74 | * @return {type} [description] 75 | */ 76 | this.getPolygons = function(){ 77 | return this.polygons; 78 | } 79 | 80 | /** 81 | * [setColor description] 82 | * @param {type} color [description] 83 | */ 84 | this.setColor = function(color){ 85 | this.color = color; 86 | } 87 | 88 | /** 89 | * [getColor description] 90 | * @return {type} [description] 91 | */ 92 | this.getColor = function(){ 93 | return this.color; 94 | } 95 | 96 | /** 97 | * [getTravelTime description] 98 | * @return {type} [description] 99 | */ 100 | this.getTravelTime = function(){ 101 | return this.travelTime; 102 | } 103 | 104 | /** 105 | * [setTravelTime description] 106 | * @param {type} travelTime [description] 107 | */ 108 | this.setTravelTime = function(travelTime){ 109 | this.travelTime = travelTime; 110 | } 111 | }; 112 | 113 | r360.multiPolygon = function () { 114 | return new r360.MultiPolygon(); 115 | }; -------------------------------------------------------------------------------- /src/geometry/types/polygon/Polygon.js: -------------------------------------------------------------------------------- 1 | r360.polygon = function (traveltime, area, outerBoundary) { 2 | return new r360.Polygon(traveltime, area, outerBoundary); 3 | }; 4 | 5 | /* 6 | * 7 | */ 8 | r360.Polygon = function(traveltime, area, outerBoundary) { 9 | 10 | this.travelTime = traveltime; 11 | this.area = area; 12 | this.color = 'black'; 13 | this.opacity = 0.5; 14 | this.lineStrings = [outerBoundary]; 15 | this.bounds = undefined; 16 | 17 | /** 18 | * [setTravelTime description] 19 | * @param {type} travelTime [description] 20 | */ 21 | this.setTravelTime = function(travelTime){ 22 | this.travelTime = travelTime; 23 | } 24 | 25 | /** 26 | * [getTravelTime description] 27 | * @return {type} [description] 28 | */ 29 | this.getTravelTime = function(){ 30 | return this.travelTime; 31 | } 32 | 33 | /** 34 | * [getColor description] 35 | * @return {type} [description] 36 | */ 37 | this.getColor = function(){ 38 | return this.color; 39 | } 40 | 41 | /** 42 | * [setColor description] 43 | * @param {type} color [description] 44 | */ 45 | this.setColor = function(color){ 46 | this.color = color; 47 | } 48 | 49 | /** 50 | * [setOpacity description] 51 | * @param {type} opacity [description] 52 | */ 53 | this.setOpacity = function(opacity){ 54 | this.opacity = opacity; 55 | } 56 | 57 | /** 58 | * [getOpacity description] 59 | * @return {type} [description] 60 | */ 61 | this.getOpacity =function(){ 62 | return this.opacity; 63 | } 64 | 65 | /** 66 | * [getArea description] 67 | * @return {type} [description] 68 | */ 69 | this.getArea = function(){ 70 | return this.area; 71 | } 72 | 73 | /** 74 | * [setArea description] 75 | * @param {type} area [description] 76 | */ 77 | this.setArea = function(area){ 78 | this.area = area; 79 | } 80 | 81 | /** 82 | * [getOuterBoundary description] 83 | * @return {type} [description] 84 | */ 85 | this.getOuterBoundary = function() { 86 | return this.lineStrings[0]; 87 | } 88 | 89 | /** 90 | * [getInnerBoundary description] 91 | * @return {type} [description] 92 | */ 93 | this.getInnerBoundary = function() { 94 | return this.lineStrings.slice(1); 95 | } 96 | 97 | /** 98 | * [getTopRight4326 description] 99 | * @return {type} [description] 100 | */ 101 | this.getTopRight4326 = function(){ 102 | return this.getOuterBoundary().getTopRight4326(); 103 | } 104 | 105 | /** 106 | * [getTopRight3857 description] 107 | * @return {type} [description] 108 | */ 109 | this.getTopRight3857 = function(){ 110 | return this.getOuterBoundary().getTopRight3857(); 111 | } 112 | 113 | /** 114 | * [getTopRightDecimal description] 115 | * @return {type} [description] 116 | */ 117 | this.getTopRightDecimal = function(){ 118 | return this.getOuterBoundary().getTopRightDecimal(); 119 | } 120 | 121 | /** 122 | * [getBottomLeft4326 description] 123 | * @return {type} [description] 124 | */ 125 | this.getBottomLeft4326 = function(){ 126 | return this.getOuterBoundary().getBottomLeft4326(); 127 | } 128 | 129 | /** 130 | * [getBottomLeft3857 description] 131 | * @return {type} [description] 132 | */ 133 | this.getBottomLeft3857 = function(){ 134 | return this.getOuterBoundary().getBottomLeft3857(); 135 | } 136 | 137 | /** 138 | * [getBottomLeftDecimal description] 139 | * @return {type} [description] 140 | */ 141 | this.getBottomLeftDecimal = function(){ 142 | return this.getOuterBoundary().getBottomLeftDecimal(); 143 | } 144 | 145 | /** 146 | * [addInnerBoundary description] 147 | * @param {type} innerBoundary [description] 148 | */ 149 | this.addInnerBoundary = function(innerBoundary){ 150 | this.lineStrings.push(innerBoundary); 151 | } 152 | } -------------------------------------------------------------------------------- /src/geometry/types/route/Route.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.Route = function(travelTime, segments, meta){ 5 | 6 | var that = this; 7 | that.travelTime = travelTime; 8 | that.routeSegments = []; 9 | that.points = []; 10 | that.uphillMeter = 0; 11 | that.downhillMeter = 0; 12 | that.targetHeight = undefined; 13 | that.sourceHeight = undefined; 14 | that.sourceId = undefined; 15 | that.targetId = undefined; 16 | that.length = undefined; 17 | that.transfers = 0; 18 | 19 | // the server delivers the route from target to source 20 | segments.reverse().forEach(function(segment){ 21 | 22 | var routeSegment = r360.routeSegment(segment); 23 | that.routeSegments.push(routeSegment); 24 | 25 | if (routeSegment.type === 'TRANSFER') that.transfers++; 26 | 27 | that.points = that.points.concat(routeSegment.getPoints().reverse()); 28 | }); 29 | 30 | 31 | if(typeof meta !== 'undefined') { 32 | that.sourceId = meta.source_id; 33 | that.targetId = meta.target_id; 34 | that.length = meta.length; 35 | } 36 | 37 | that.equals = function(route) { 38 | return that.getKey() === route.getKey(); 39 | }; 40 | 41 | that.getKey = function(){ 42 | 43 | var key = travelTime; 44 | var points = ""; 45 | 46 | that.getSegments().forEach(function(segment){ 47 | 48 | key += " " + segment.getRouteShortName() + " " + segment.getDepartureTime() + " " + segment.getArrivalTime(); 49 | 50 | segment.getPoints().forEach(function(point){ points += " " + point.lat + "" + point.lng; }); 51 | }); 52 | 53 | return key + points; 54 | } 55 | 56 | /* 57 | * 58 | */ 59 | that.addRouteSegment = function(routeSegment){ 60 | that.routeSegments.push(routeSegment); 61 | } 62 | 63 | /* 64 | * 65 | */ 66 | that.setTravelTime = function(travelTime){ 67 | that.travelTime = travelTime; 68 | } 69 | 70 | /* 71 | * 72 | */ 73 | 74 | that.getDistance = function(){ 75 | var distance = 0; 76 | for(var i = 0; i < that.routeSegments.length; i++){ 77 | distance += that.routeSegments[i].getDistance(); 78 | } 79 | return distance; 80 | } 81 | 82 | /** 83 | * [getElevationGain description] 84 | * @return {type} [description] 85 | */ 86 | that.getElevationGain = function(){ 87 | var distance = 0; 88 | for(var i = 0; i < that.routeSegments.length; i++){ 89 | distance += that.routeSegments[i].getElevationGain(); 90 | } 91 | return distance; 92 | } 93 | 94 | /** 95 | * [getElevations description] 96 | * @return {type} [description] 97 | */ 98 | that.getElevations = function() { 99 | 100 | var elevations = { x : [] , y : []}; 101 | for ( var i = 0 ; i < that.getDistance() * 1000 ; i = i + 100 ) { 102 | elevations.x.push((i / 1000) + " km" ); 103 | elevations.y.push(that.getElevationAt(i)); 104 | } 105 | 106 | return elevations; 107 | } 108 | 109 | /** 110 | * [getElevationAt description] 111 | * @param {type} meter [description] 112 | * @return {type} [description] 113 | */ 114 | that.getElevationAt = function(meter) { 115 | 116 | var currentLength = 0; 117 | 118 | for ( var i = 1 ; i < that.points.length ; i++ ){ 119 | 120 | var previousPoint = that.points[i - 1]; 121 | var currentPoint = that.points[i]; 122 | var currentDistance = previousPoint.distanceTo(currentPoint); 123 | 124 | currentLength += currentDistance; 125 | 126 | if ( currentLength > meter ) return currentPoint.alt; 127 | } 128 | } 129 | 130 | /* 131 | * 132 | */ 133 | that.getSegments = function(){ 134 | return that.routeSegments; 135 | } 136 | 137 | that.getUphillElevation = function() { 138 | return that.uphillMeter; 139 | } 140 | 141 | that.getDownhillElevation = function() { 142 | return that.downhillMeter; 143 | } 144 | 145 | that.getTotalElevationDifference = function(){ 146 | return Math.abs(that.sourceHeight - that.targetHeight); 147 | } 148 | 149 | that.setElevationDifferences = function() { 150 | 151 | var previousHeight = undefined; 152 | 153 | for ( var i = that.points.length - 1; i >= 0 ; i-- ) { 154 | 155 | if ( i == 0 ) that.targetHeight = that.points[i].alt; 156 | if ( i == that.points.length - 1 ) that.sourceHeight = that.points[i].alt; 157 | 158 | if ( typeof previousHeight != 'undefined' ) { 159 | 160 | // we go up 161 | if ( previousHeight > that.points[i].alt ) 162 | that.uphillMeter += (previousHeight - that.points[i].alt); 163 | // and down 164 | else if ( previousHeight < that.points[i].alt ) 165 | that.downhillMeter += (that.points[i].alt - previousHeight); 166 | } 167 | 168 | previousHeight = that.points[i].alt; 169 | } 170 | }(); 171 | 172 | /* 173 | * 174 | */ 175 | that.getTravelTime = function(){ 176 | return that.travelTime; 177 | }; 178 | }; 179 | 180 | r360.route = function (travelTime, segments, meta) { 181 | return new r360.Route(travelTime, segments, meta); 182 | }; -------------------------------------------------------------------------------- /src/geometry/types/route/RouteSegment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.RouteSegment = function(segment){ 5 | 6 | var that = this; 7 | that.points = []; 8 | that.type = segment.type; 9 | that.travelTime = segment.travelTime; 10 | 11 | /* 12 | * TODO don't call it length! in route length refers to the array length. 13 | * Call it distance instead 14 | */ 15 | 16 | that.distance = segment.length / 1000; 17 | that.warning = segment.warning; 18 | that.elevationGain = segment.elevationGain; 19 | that.errorMessage; 20 | that.transitSegment = false; 21 | that.startname = segment.startname; 22 | that.endname = segment.endname; 23 | 24 | // build the geometry 25 | segment.points.forEach(function(point){ 26 | that.points.push(r360.Util.webMercatorToLatLng(new r360.Point(point[1], point[0]), point[2])); 27 | }); 28 | 29 | // in case we have a transit route, we set a color depending 30 | // on the route type (bus, subway, tram etc.) 31 | // and we set information which are only available 32 | // for transit segments like depature station and route short sign 33 | if ( segment.isTransit ) { 34 | 35 | var colorObject = r360.findWhere(r360.config.routeTypes, {routeType : segment.routeType}); 36 | that.color = typeof colorObject != 'undefined' && r360.has(colorObject, 'color') ? colorObject.color : 'RED'; 37 | that.haloColor = typeof colorObject != 'undefined' && r360.has(colorObject, 'haloColor') ? colorObject.haloColor : 'WHITE'; 38 | that.transitSegment = true; 39 | that.routeType = segment.routeType; 40 | that.routeShortName = segment.routeShortName; 41 | that.startname = segment.startname; 42 | that.endname = segment.endname; 43 | that.departureTime = segment.departureTime; 44 | that.arrivalTime = segment.arrivalTime; 45 | that.tripHeadSign = segment.tripHeadSign; 46 | } 47 | else { 48 | 49 | var colorObject = r360.findWhere(r360.config.routeTypes, {routeType : segment.type}); 50 | that.color = typeof colorObject != 'undefined' && r360.has(colorObject, 'color') ? colorObject.color : 'RED'; 51 | that.haloColor = typeof colorObject != 'undefined' && r360.has(colorObject, 'haloColor') ? colorObject.haloColor : 'WHITE'; 52 | } 53 | 54 | that.getPoints = function(){ 55 | return that.points; 56 | } 57 | 58 | that.getType = function(){ 59 | return that.type; 60 | } 61 | 62 | that.getHaloColor = function(){ 63 | return that.haloColor; 64 | } 65 | 66 | that.getColor = function(){ 67 | return that.color; 68 | } 69 | 70 | that.getTravelTime = function(){ 71 | return that.travelTime; 72 | } 73 | 74 | that.getDistance = function(){ 75 | return that.distance; 76 | } 77 | 78 | that.getRouteType = function(){ 79 | return that.routeType; 80 | } 81 | 82 | that.getRouteShortName = function(){ 83 | return that.routeShortName; 84 | } 85 | 86 | that.getStartName = function(){ 87 | return that.startname; 88 | } 89 | 90 | that.getEndName = function(){ 91 | return that.endname; 92 | } 93 | 94 | that.getDepartureTime = function(){ 95 | return that.departureTime; 96 | } 97 | 98 | that.getArrivalTime = function(){ 99 | return that.arrivalTime; 100 | } 101 | 102 | that.getTripHeadSign = function(){ 103 | return that.tripHeadSign; 104 | } 105 | 106 | that.getWarning = function(){ 107 | return that.warning; 108 | } 109 | 110 | that.getElevationGain = function(){ 111 | return that.elevationGain; 112 | } 113 | 114 | that.isTransit = function(){ 115 | return that.transitSegment; 116 | } 117 | }; 118 | 119 | r360.routeSegment = function (segment) { 120 | return new r360.RouteSegment(segment); 121 | }; -------------------------------------------------------------------------------- /src/r360-defaults.js: -------------------------------------------------------------------------------- 1 | r360.config = { 2 | 3 | serviceVersion : 'v1', 4 | pathSerializer : 'compact', 5 | requestTimeout : 10000, 6 | maxRoutingTime : 3600, 7 | maxRoutingLength : 100000, 8 | bikeSpeed : 15, 9 | bikeUphill : 20, 10 | bikeDownhill : -10, 11 | walkSpeed : 5, 12 | walkUphill : 10, 13 | walkDownhill : 0, 14 | travelTimes : [300, 600, 900, 1200, 1500, 1800], 15 | travelType : "walk", 16 | logging : false, 17 | rushHour : false, 18 | 19 | // options for the travel time slider; colors and lengths etc. 20 | defaultTravelTimeControlOptions : { 21 | travelTimes : [ 22 | { time : 300 , color : "#006837", opacity : 0.1 }, 23 | { time : 600 , color : "#39B54A", opacity : 0.2 }, 24 | { time : 900 , color : "#8CC63F", opacity : 0.3 }, 25 | { time : 1200 , color : "#F7931E", opacity : 0.4 }, 26 | { time : 1500 , color : "#F15A24", opacity : 0.5 }, 27 | { time : 1800 , color : "#C1272D", opacity : 1.0 } 28 | ], 29 | position : 'topright', 30 | label: 'travel time', 31 | initValue: 30 32 | }, 33 | 34 | routeTypes : [ 35 | 36 | // non transit 37 | { routeType : 'WALK' , color : "red", haloColor : "white"}, 38 | { routeType : 'BIKE' , color : "#558D54", haloColor : "white"}, 39 | { routeType : 'CAR' , color : "#558D54", haloColor : "white"}, 40 | { routeType : 'TRANSFER' , color : "#C1272D", haloColor : "white"}, 41 | 42 | // berlin 43 | { routeType : 102 , color : "#006837", haloColor : "white" }, 44 | { routeType : 400 , color : "#156ab8", haloColor : "white" }, 45 | { routeType : 900 , color : "red", haloColor : "white" }, 46 | { routeType : 700 , color : "#A3007C", haloColor : "white" }, 47 | { routeType : 1000 , color : "blue", haloColor : "white" }, 48 | { routeType : 109 , color : "#006F35", haloColor : "white" }, 49 | { routeType : 100 , color : "red", haloColor : "white" }, 50 | // new york 51 | { routeType : 1 , color : "red", haloColor : "red"}, 52 | { routeType : 2 , color : "blue", haloColor : "blue"}, 53 | { routeType : 3 , color : "yellow", haloColor : "yellow"}, 54 | { routeType : 0 , color : "green", haloColor : "green"}, 55 | { routeType : 4 , color : "orange", haloColor : "orange"}, 56 | { routeType : 5 , color : "red", haloColor : "red"}, 57 | { routeType : 6 , color : "blue", haloColor : "blue"}, 58 | { routeType : 7 , color : "yellow", haloColor : "yellow" } 59 | ], 60 | 61 | photonPlaceAutoCompleteOptions : { 62 | serviceUrl : "https://service.route360.net/geocode/", 63 | position : 'topleft', 64 | reset : false, 65 | reverse : false, 66 | placeholder : 'Select source', 67 | maxRows : 5, 68 | width : 300 69 | }, 70 | 71 | defaultRadioOptions: { 72 | position : 'topright', 73 | }, 74 | 75 | // configuration for the Route360PolygonLayer 76 | defaultPolygonLayerOptions:{ 77 | opacity : 0.4, 78 | strokeWidth: 30, 79 | 80 | tolerance: 15, 81 | 82 | // background values only matter if inverse = true 83 | backgroundColor : 'black', 84 | backgroundOpacity : 0.5, 85 | inverse : false, 86 | 87 | animate : false, 88 | animationDuration : 1 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/r360.js: -------------------------------------------------------------------------------- 1 | 2 | var r360 = { 3 | version : 'v0.5.3', 4 | 5 | // Is a given variable undefined? 6 | isUndefined : function(obj) { 7 | return obj === void 0; 8 | }, 9 | 10 | // Shortcut function for checking if an object has a given property directly 11 | // on itself (in other words, not on a prototype). 12 | has : function(obj, key) { 13 | return obj != null && hasOwnProperty.call(obj, key); 14 | }, 15 | 16 | // is a given object a function 17 | isFunction : function(obj) { 18 | return typeof obj == 'function' || false; 19 | }, 20 | 21 | findWhere : function(array, attr) { 22 | var result = undefined; 23 | array.some(function(elem,index,array){ 24 | var match = false; 25 | for(var index in attr) { 26 | match = (r360.has(elem,index) && elem[index] === attr[index]) ? true : false; 27 | } 28 | if (match) { 29 | result = elem; 30 | return true; 31 | } 32 | }); 33 | return result; 34 | }, 35 | 36 | filter : function(array,predicate) { 37 | var results = []; 38 | array.forEach(function(elem,index,array){ 39 | if (predicate(elem, index, array)) results.push(elem); 40 | }); 41 | return results; 42 | }, 43 | 44 | contains : function(array,item) { 45 | return array.indexOf(item) > -1; 46 | }, 47 | 48 | each : function(array,cb) { 49 | array.forEach(function(elem,index,array){ 50 | cb(elem,index,array); 51 | }); 52 | }, 53 | 54 | max : function(array, iteratee, context) { 55 | var result = -Infinity, lastComputed = -Infinity, 56 | value, computed; 57 | if (iteratee == null || (typeof iteratee == 'number' && typeof array[0] != 'arrayect') && array != null) { 58 | for (var i = 0, length = array.length; i < length; i++) { 59 | value = array[i]; 60 | if (value != null && value > result) { 61 | result = value; 62 | } 63 | } 64 | } else { 65 | r360.each(array, function(elem, index, array) { 66 | computed = iteratee(elem, index, array); 67 | if (computed > lastComputed || computed === -Infinity && result === -Infinity) { 68 | result = elem; 69 | lastComputed = computed; 70 | } 71 | }); 72 | } 73 | return result; 74 | }, 75 | 76 | keys : function(obj) { 77 | if (typeof obj !== 'Object') return []; 78 | if (Object.keys(obj)) return Object.keys(obj); 79 | var keys = []; 80 | for (var key in obj) if (r360.has(obj, key)) keys.push(key); 81 | return keys; 82 | } 83 | 84 | }; 85 | 86 | function expose() { 87 | var oldr360 = window.r360; 88 | 89 | r360.noConflict = function () { 90 | window.r360 = oldr360; 91 | return this; 92 | }; 93 | 94 | window.r360 = r360; 95 | } 96 | 97 | // define r360 for Node module pattern loaders, including Browserify 98 | if (typeof module === 'object' && typeof module.exports === 'object') 99 | module.exports = r360; 100 | 101 | // define r360 as an AMD module 102 | else if (typeof define === 'function' && define.amd) define(r360); 103 | 104 | // define r360 as a global r360 variable, saving the original r360 to restore later if needed 105 | else expose(); 106 | 107 | 108 | /* 109 | * IE 8 does not get the bind function. This is a workaround... 110 | */ 111 | if (!Function.prototype.bind) { 112 | Function.prototype.bind = function (oThis) { 113 | if (typeof this !== "function") { 114 | // closest thing possible to the ECMAScript 5 internal IsCallable function 115 | throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); 116 | } 117 | 118 | var aArgs = Array.prototype.slice.call(arguments, 1), 119 | fToBind = this, 120 | fNOP = function () {}, 121 | fBound = function () { 122 | return fToBind.apply(this instanceof fNOP && oThis 123 | ? this 124 | : oThis, 125 | aArgs.concat(Array.prototype.slice.call(arguments))); 126 | }; 127 | 128 | fNOP.prototype = this.prototype; 129 | fBound.prototype = new fNOP(); 130 | 131 | return fBound; 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /src/rest/polygons/PolygonService.js: -------------------------------------------------------------------------------- 1 | 2 | r360.PolygonService = { 3 | 4 | cache : {}, 5 | 6 | getCfg : function(travelOptions){ 7 | 8 | // we only need the source points for the polygonizing and the polygon travel times 9 | var cfg = {}; 10 | cfg.sources = []; 11 | 12 | if ( !r360.isUndefined(travelOptions.isElevationEnabled()) ) cfg.elevation = travelOptions.isElevationEnabled(); 13 | if ( !r360.isUndefined(travelOptions.getReverse()) ) cfg.reverse = travelOptions.getReverse(); 14 | if ( !r360.isUndefined(travelOptions.getEdgeWeight()) ) cfg.edgeWeight = travelOptions.getEdgeWeight(); 15 | if ( !r360.isUndefined(travelOptions.getTravelTimeFactors()) ) cfg.travelTimeFactors = travelOptions.getTravelTimeFactors(); 16 | 17 | cfg.polygon = {}; 18 | 19 | if ( !r360.isUndefined(travelOptions.getTravelTimes()) ) cfg.polygon.values = travelOptions.getTravelTimes(); 20 | if ( !r360.isUndefined(travelOptions.getIntersectionMode()) ) cfg.polygon.intersectionMode = travelOptions.getIntersectionMode(); 21 | if ( !r360.isUndefined(travelOptions.getPolygonSerializer()) ) cfg.polygon.serializer = travelOptions.getPolygonSerializer(); 22 | if ( !r360.isUndefined(travelOptions.isPointReductionEnabled()) ) cfg.polygon.pointReduction = travelOptions.isPointReductionEnabled(); 23 | if ( !r360.isUndefined(travelOptions.getMinPolygonHoleSize()) ) cfg.polygon.minPolygonHoleSize = travelOptions.getMinPolygonHoleSize(); 24 | if ( !r360.isUndefined(travelOptions.getSrid()) ) cfg.polygon.srid = travelOptions.getSrid(); 25 | if ( !r360.isUndefined(travelOptions.getSimplifyMeter()) ) cfg.polygon.simplify = travelOptions.getSimplifyMeter(); 26 | if ( !r360.isUndefined(travelOptions.getBuffer()) ) cfg.polygon.buffer = travelOptions.getBuffer(); 27 | if ( !r360.isUndefined(travelOptions.getQuadrantSegments()) ) cfg.polygon.quadrantSegments = travelOptions.getQuadrantSegments(); 28 | 29 | // add each source point and it's travel configuration to the cfg 30 | travelOptions.getSources().forEach(function(source){ 31 | 32 | var src = { 33 | lat : r360.has(source, 'lat') ? source.lat : source.getLatLng().lat, 34 | lng : r360.has(source, 'lon') ? source.lon : r360.has(source, 'lng') ? source.lng : source.getLatLng().lng, 35 | id : r360.has(source, 'id') ? source.id : '', 36 | tm : {} 37 | }; 38 | 39 | var travelType = r360.has(source, 'travelType') ? source.travelType : travelOptions.getTravelType(); 40 | 41 | // this enables car routing 42 | src.tm[travelType] = {}; 43 | 44 | // set special routing parameters depending on the travel type 45 | if ( travelType == 'transit' || travelType == 'biketransit' ) { 46 | 47 | src.tm[travelType].frame = {}; 48 | if ( !r360.isUndefined(travelOptions.getTime()) ) src.tm[travelType].frame.time = travelOptions.getTime(); 49 | if ( !r360.isUndefined(travelOptions.getDate()) ) src.tm[travelType].frame.date = travelOptions.getDate(); 50 | if ( !r360.isUndefined(travelOptions.getFrameDuration()) ) src.tm[travelType].frame.duration = travelOptions.getFrameDuration(); 51 | if ( !r360.isUndefined(travelOptions.getMaxTransfers()) ) src.tm[travelType].maxTransfers = travelOptions.getMaxTransfers(); 52 | } 53 | if ( travelType == 'bike' ) { 54 | 55 | src.tm.bike = {}; 56 | if ( !r360.isUndefined(travelOptions.getBikeSpeed()) ) src.tm.bike.speed = travelOptions.getBikeSpeed(); 57 | if ( !r360.isUndefined(travelOptions.getBikeUphill()) ) src.tm.bike.uphill = travelOptions.getBikeUphill(); 58 | if ( !r360.isUndefined(travelOptions.getBikeDownhill()) ) src.tm.bike.downhill = travelOptions.getBikeDownhill(); 59 | } 60 | if ( travelType == 'walk') { 61 | 62 | src.tm.walk = {}; 63 | if ( !r360.isUndefined(travelOptions.getWalkSpeed()) ) src.tm.walk.speed = travelOptions.getWalkSpeed(); 64 | if ( !r360.isUndefined(travelOptions.getWalkUphill()) ) src.tm.walk.uphill = travelOptions.getWalkUphill(); 65 | if ( !r360.isUndefined(travelOptions.getWalkDownhill()) ) src.tm.walk.downhill = travelOptions.getWalkDownhill(); 66 | } 67 | if (travelType == 'car') { 68 | src.tm[travelType].rushHour = false; 69 | if ( !r360.isUndefined(travelOptions.isRushHour()) ) src.tm[travelType].rushHour = travelOptions.isRushHour(); 70 | } 71 | 72 | cfg.sources.push(src); 73 | }); 74 | 75 | return cfg; 76 | }, 77 | 78 | /* 79 | * 80 | */ 81 | getTravelTimePolygons : function(travelOptions, successCallback, errorCallback, method) { 82 | 83 | var cfg = r360.PolygonService.getCfg(travelOptions); 84 | 85 | if ( !r360.has(r360.PolygonService.cache, JSON.stringify(cfg)) ) { 86 | 87 | var options = r360.PolygonService.getAjaxOptions(travelOptions, cfg, successCallback, errorCallback, typeof method == 'undefined' ? 'GET' : method); 88 | 89 | // make the request to the Route360° backend 90 | // use GET as fallback, otherwise use the supplied option 91 | $.ajax(options); 92 | } 93 | else { 94 | 95 | // call successCallback with returned results 96 | successCallback(travelOptions.getPolygonSerializer() == 'geojson' ? r360.PolygonService.cache[JSON.stringify(cfg)] : r360.Util.parsePolygons(r360.PolygonService.cache[JSON.stringify(cfg)])); 97 | } 98 | }, 99 | 100 | /** 101 | * [getAjaxOptions description] 102 | * @param {type} travelOptions [description] 103 | * @param {type} successCallback [description] 104 | * @param {type} errorCallback [description] 105 | * @return {type} [description] 106 | */ 107 | getAjaxOptions : function(travelOptions, cfg, successCallback, errorCallback, method) { 108 | 109 | var serviceUrl = typeof travelOptions.getServiceUrl() !== 'undefined' ? travelOptions.getServiceUrl() : r360.config.serviceUrl; 110 | 111 | var options = { 112 | url : serviceUrl + r360.config.serviceVersion + '/polygon?cfg=' + encodeURIComponent(JSON.stringify(cfg)) + '&cb=?&key='+travelOptions.getServiceKey(), 113 | timeout : r360.config.requestTimeout, 114 | dataType : "json", 115 | type : method, 116 | success : function(result) { 117 | 118 | // the new version is an object, old one an array 119 | if ( r360.has(result, 'data') ) { 120 | 121 | if ( result.code == 'ok' ) { 122 | 123 | // cache the result 124 | r360.PolygonService.cache[JSON.stringify(cfg)] = result.data; 125 | // call successCallback with returned results 126 | successCallback(travelOptions.getPolygonSerializer() == 'geojson' ? result.data : r360.Util.parsePolygons(result.data)); 127 | } 128 | else 129 | // check if the error callback is defined 130 | if ( r360.isFunction(errorCallback) ) 131 | errorCallback(result.code, result.message); 132 | } 133 | // fallback for old clients 134 | else { 135 | 136 | // cache the result 137 | r360.PolygonService.cache[JSON.stringify(cfg)] = result; 138 | // call successCallback with returned results 139 | successCallback(travelOptions.getPolygonSerializer() == 'geojson' ? result.data : r360.Util.parsePolygons(result)); 140 | } 141 | }, 142 | // this only happens if the service is not available, all other errors have to be transmitted in the response 143 | error: function(data){ 144 | 145 | // call error callback if defined 146 | if ( r360.isFunction(errorCallback) ) { 147 | 148 | if ( data.status == 403 ) 149 | errorCallback("not-authorized", data.responseText); 150 | else 151 | errorCallback("service-not-available", "The travel time polygon service is currently not available, please try again later."); 152 | } 153 | } 154 | }; 155 | 156 | if ( method == 'POST' ) { 157 | 158 | options.url = serviceUrl + r360.config.serviceVersion + '/polygon?key=' + travelOptions.getServiceKey(); 159 | options.data = JSON.stringify(cfg); 160 | options.contentType = 'application/json'; 161 | options.async = false; 162 | } 163 | 164 | return options; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/rest/routes/RouteService.js: -------------------------------------------------------------------------------- 1 | r360.RouteService = { 2 | 3 | cache : {}, 4 | 5 | /* 6 | * 7 | */ 8 | getCfg : function(travelOptions){ 9 | 10 | var cfg = { sources : [], targets : [], pathSerializer : travelOptions.getPathSerializer() }; 11 | 12 | if ( !r360.isUndefined(travelOptions.isElevationEnabled()) ) cfg.elevation = travelOptions.isElevationEnabled(); 13 | if ( !r360.isUndefined(travelOptions.getReverse()) ) cfg.reverse = travelOptions.getReverse(); 14 | if ( !r360.isUndefined(travelOptions.getTravelTimeFactors()) ) cfg.travelTimeFactors = travelOptions.getTravelTimeFactors(); 15 | 16 | travelOptions.getSources().forEach(function(source){ 17 | 18 | // set the basic information for this source 19 | var src = { 20 | lat : r360.has(source, 'lat') ? source.lat : source.getLatLng().lat, 21 | lng : r360.has(source, 'lon') ? source.lon : r360.has(source, 'lng') ? source.lng : source.getLatLng().lng, 22 | id : r360.has(source, 'id') ? source.id : '', 23 | tm : {} 24 | }; 25 | 26 | var travelType = r360.has(source, 'travelType') ? source.travelType : travelOptions.getTravelType(); 27 | 28 | src.tm[travelType] = {}; 29 | 30 | // set special routing parameters depending on the travel type 31 | if ( travelType == 'transit' || travelType == 'biketransit' ) { 32 | 33 | src.tm[travelType].frame = {}; 34 | if ( !r360.isUndefined(travelOptions.getTime()) ) src.tm[travelType].frame.time = travelOptions.getTime(); 35 | if ( !r360.isUndefined(travelOptions.getDate()) ) src.tm[travelType].frame.date = travelOptions.getDate(); 36 | if ( !r360.isUndefined(travelOptions.getRecommendations()) ) src.tm[travelType].recommendations = travelOptions.getRecommendations(); 37 | if ( !r360.isUndefined(travelOptions.getFrameDuration()) ) src.tm[travelType].frame.duration = travelOptions.getFrameDuration(); 38 | if ( !r360.isUndefined(travelOptions.getMaxTransfers()) ) src.tm[travelType].maxTransfers = travelOptions.getMaxTransfers(); 39 | } 40 | if ( travelType == 'bike' ) { 41 | 42 | src.tm.bike = {}; 43 | if ( !r360.isUndefined(travelOptions.getBikeSpeed()) ) src.tm.bike.speed = travelOptions.getBikeSpeed(); 44 | if ( !r360.isUndefined(travelOptions.getBikeUphill()) ) src.tm.bike.uphill = travelOptions.getBikeUphill(); 45 | if ( !r360.isUndefined(travelOptions.getBikeDownhill()) ) src.tm.bike.downhill = travelOptions.getBikeDownhill(); 46 | } 47 | if ( travelType == 'walk') { 48 | 49 | src.tm.walk = {}; 50 | if ( !r360.isUndefined(travelOptions.getWalkSpeed()) ) src.tm.walk.speed = travelOptions.getWalkSpeed(); 51 | if ( !r360.isUndefined(travelOptions.getWalkUphill()) ) src.tm.walk.uphill = travelOptions.getWalkUphill(); 52 | if ( !r360.isUndefined(travelOptions.getWalkDownhill()) ) src.tm.walk.downhill = travelOptions.getWalkDownhill(); 53 | } 54 | if (travelType == 'car') { 55 | src.tm[travelType].rushHour = false; 56 | if ( !r360.isUndefined(travelOptions.isRushHour()) ) src.tm[travelType].rushHour = travelOptions.isRushHour(); 57 | } 58 | 59 | // add it to the list of sources 60 | cfg.sources.push(src); 61 | }); 62 | 63 | cfg.targets = []; 64 | travelOptions.getTargets().forEach(function(target){ 65 | 66 | cfg.targets.push({ 67 | 68 | lat : r360.has(target, 'lat') ? target.lat : target.getLatLng().lat, 69 | lng : r360.has(target, 'lon') ? target.lon : r360.has(target, 'lng') ? target.lng : target.getLatLng().lng, 70 | id : r360.has(target, 'id') ? target.id : '', 71 | }); 72 | }); 73 | 74 | return cfg; 75 | }, 76 | 77 | /* 78 | * 79 | */ 80 | getRoutes : function(travelOptions, successCallback, errorCallback) { 81 | 82 | var cfg = r360.RouteService.getCfg(travelOptions); 83 | 84 | if ( !r360.has(r360.RouteService.cache, JSON.stringify(cfg)) ) { 85 | 86 | // make the request to the Route360° backend 87 | $.ajax({ 88 | url : travelOptions.getServiceUrl() + r360.config.serviceVersion + '/route?cfg=' + encodeURIComponent(JSON.stringify(cfg)) + "&cb=?&key="+travelOptions.getServiceKey(), 89 | timeout : r360.config.requestTimeout, 90 | dataType : "json", 91 | success : function(result) { 92 | 93 | // the new version is an object, old one an array 94 | if ( r360.has(result, 'data') ) { 95 | 96 | if ( result.code == 'ok' ) { 97 | 98 | // cache the result 99 | r360.RouteService.cache[JSON.stringify(cfg)] = JSON.parse(JSON.stringify(result.data)); 100 | // call successCallback with returned results 101 | successCallback(r360.Util.parseRoutes(result.data)); 102 | } 103 | else 104 | // check if the error callback is defined 105 | if ( r360.isFunction(errorCallback) ) 106 | errorCallback(result.code, result.message); 107 | } 108 | // fallback for old clients 109 | else { 110 | 111 | // cache the result 112 | r360.RouteService.cache[JSON.stringify(cfg)] = JSON.parse(JSON.stringify(result)); 113 | // call successCallback with returned results 114 | successCallback(r360.Util.parseRoutes(result)); 115 | } 116 | }, 117 | // this only happens if the service is not available, all other errors have to be transmitted in the response 118 | error: function(data){ 119 | 120 | // call error callback if defined 121 | if ( r360.isFunction(errorCallback) ) { 122 | 123 | if ( data.status == 403 ) 124 | errorCallback("not-authorized", data.responseText); 125 | else 126 | errorCallback("service-not-available", "The routing service is currently not available, please try again later."); 127 | } 128 | } 129 | }); 130 | } 131 | else { 132 | 133 | // call callback with returned results 134 | successCallback(r360.Util.parseRoutes(JSON.parse(JSON.stringify(r360.RouteService.cache[JSON.stringify(cfg)])))); 135 | } 136 | } 137 | }; 138 | -------------------------------------------------------------------------------- /src/rest/time/TimeService.js: -------------------------------------------------------------------------------- 1 | r360.TimeService = { 2 | 3 | cache : {}, 4 | 5 | getCfg : function(travelOptions) { 6 | 7 | var cfg = { 8 | sources : [], targets : [], 9 | pathSerializer : travelOptions.getPathSerializer(), 10 | maxRoutingTime : travelOptions.getMaxRoutingTime(), 11 | maxRoutingLength : travelOptions.getMaxRoutingLength() 12 | }; 13 | 14 | if ( !r360.isUndefined(travelOptions.isElevationEnabled()) ) cfg.elevation = travelOptions.isElevationEnabled(); 15 | if ( !r360.isUndefined(travelOptions.getTravelTimeFactors()) ) cfg.travelTimeFactors = travelOptions.getTravelTimeFactors(); 16 | if ( !r360.isUndefined(travelOptions.getTravelTimes()) || !r360.isUndefined(travelOptions.getIntersectionMode()) ) { 17 | 18 | cfg.polygon = {}; 19 | 20 | if ( !r360.isUndefined(travelOptions.getTravelTimes()) ) cfg.polygon.values = travelOptions.getTravelTimes(); 21 | if ( !r360.isUndefined(travelOptions.getIntersectionMode()) ) cfg.polygon.intersectionMode = travelOptions.getIntersectionMode(); 22 | if ( !r360.isUndefined(travelOptions.getMinPolygonHoleSize()) ) cfg.polygon.minPolygonHoleSize = travelOptions.getMinPolygonHoleSize(); 23 | } 24 | 25 | // configure sources 26 | travelOptions.getSources().forEach(function(source){ 27 | 28 | // set the basic information for this source 29 | var src = { 30 | lat : r360.has(source, 'lat') ? source.lat : source.getLatLng().lat, 31 | lng : r360.has(source, 'lon') ? source.lon : r360.has(source, 'lng') ? source.lng : source.getLatLng().lng, 32 | id : r360.has(source, 'id') ? source.id : '', 33 | tm : {} 34 | }; 35 | 36 | if ( src.id == '' ) src.id = src.lat + ';' + src.lng; 37 | 38 | var travelType = r360.has(source, 'travelType') ? source.travelType : travelOptions.getTravelType(); 39 | 40 | // this enables car routing 41 | src.tm[travelType] = {}; 42 | 43 | // set special routing parameters depending on the travel type 44 | if ( travelType == 'transit' || travelType == 'biketransit' ) { 45 | 46 | src.tm[travelType].frame = {}; 47 | if ( !r360.isUndefined(travelOptions.getTime()) ) src.tm[travelType].frame.time = travelOptions.getTime(); 48 | if ( !r360.isUndefined(travelOptions.getDate()) ) src.tm[travelType].frame.date = travelOptions.getDate(); 49 | if ( !r360.isUndefined(travelOptions.getFrameDuration()) ) src.tm[travelType].frame.duration = travelOptions.getFrameDuration(); 50 | if ( !r360.isUndefined(travelOptions.getMaxTransfers()) ) src.tm[travelType].maxTransfers = travelOptions.getMaxTransfers(); 51 | } 52 | if ( travelType == 'ebike' ) { 53 | 54 | src.tm.ebike = {}; 55 | if ( !r360.isUndefined(travelOptions.getBikeSpeed()) ) src.tm.ebike.speed = travelOptions.getBikeSpeed(); 56 | if ( !r360.isUndefined(travelOptions.getBikeUphill()) ) src.tm.ebike.uphill = travelOptions.getBikeUphill(); 57 | if ( !r360.isUndefined(travelOptions.getBikeDownhill()) ) src.tm.ebike.downhill = travelOptions.getBikeDownhill(); 58 | } 59 | if ( travelType == 'rentbike' ) { 60 | 61 | src.tm.rentbike = {}; 62 | if ( !r360.isUndefined(travelOptions.getBikeSpeed()) ) src.tm.rentbike.bikespeed = travelOptions.getBikeSpeed(); 63 | if ( !r360.isUndefined(travelOptions.getBikeUphill()) ) src.tm.rentbike.bikeuphill = travelOptions.getBikeUphill(); 64 | if ( !r360.isUndefined(travelOptions.getBikeDownhill()) ) src.tm.rentbike.bikedownhill = travelOptions.getBikeDownhill(); 65 | if ( !r360.isUndefined(travelOptions.getWalkSpeed()) ) src.tm.rentbike.walkspeed = travelOptions.getWalkSpeed(); 66 | if ( !r360.isUndefined(travelOptions.getWalkUphill()) ) src.tm.rentbike.walkuphill = travelOptions.getWalkUphill(); 67 | if ( !r360.isUndefined(travelOptions.getWalkDownhill()) ) src.tm.rentbike.walkdownhill = travelOptions.getWalkDownhill(); 68 | } 69 | if ( travelType == 'rentandreturnbike' ) { 70 | 71 | src.tm.rentandreturnbike = {}; 72 | if ( !r360.isUndefined(travelOptions.getBikeSpeed()) ) src.tm.rentandreturnbike.bikespeed = travelOptions.getBikeSpeed(); 73 | if ( !r360.isUndefined(travelOptions.getBikeUphill()) ) src.tm.rentandreturnbike.bikeuphill = travelOptions.getBikeUphill(); 74 | if ( !r360.isUndefined(travelOptions.getBikeDownhill()) ) src.tm.rentandreturnbike.bikedownhill = travelOptions.getBikeDownhill(); 75 | if ( !r360.isUndefined(travelOptions.getWalkSpeed()) ) src.tm.rentandreturnbike.walkspeed = travelOptions.getWalkSpeed(); 76 | if ( !r360.isUndefined(travelOptions.getWalkUphill()) ) src.tm.rentandreturnbike.walkuphill = travelOptions.getWalkUphill(); 77 | if ( !r360.isUndefined(travelOptions.getWalkDownhill()) ) src.tm.rentandreturnbike.walkdownhill = travelOptions.getWalkDownhill(); 78 | } 79 | if ( travelType == 'bike' ) { 80 | 81 | src.tm.bike = {}; 82 | if ( !r360.isUndefined(travelOptions.getBikeSpeed()) ) src.tm.bike.speed = travelOptions.getBikeSpeed(); 83 | if ( !r360.isUndefined(travelOptions.getBikeUphill()) ) src.tm.bike.uphill = travelOptions.getBikeUphill(); 84 | if ( !r360.isUndefined(travelOptions.getBikeDownhill()) ) src.tm.bike.downhill = travelOptions.getBikeDownhill(); 85 | } 86 | if ( travelType == 'walk') { 87 | 88 | src.tm.walk = {}; 89 | if ( !r360.isUndefined(travelOptions.getWalkSpeed()) ) src.tm.walk.speed = travelOptions.getWalkSpeed(); 90 | if ( !r360.isUndefined(travelOptions.getWalkUphill()) ) src.tm.walk.uphill = travelOptions.getWalkUphill(); 91 | if ( !r360.isUndefined(travelOptions.getWalkDownhill()) ) src.tm.walk.downhill = travelOptions.getWalkDownhill(); 92 | } 93 | if (travelType == 'car') { 94 | src.tm.car.rushHour = false; 95 | if ( !r360.isUndefined(travelOptions.isRushHour()) ) src.tm.car.rushHour = travelOptions.isRushHour(); 96 | } 97 | // add to list of sources 98 | cfg.sources.push(src); 99 | }); 100 | 101 | // configure targets for routing 102 | travelOptions.getTargets().forEach(function(target){ 103 | 104 | var target = { 105 | 106 | lat : r360.has(target, 'lat') ? target.lat : target.getLatLng().lat, 107 | lng : r360.has(target, 'lon') ? target.lon : r360.has(target, 'lng') ? target.lng : target.getLatLng().lng, 108 | id : r360.has(target, 'id') ? target.id : '', 109 | }; 110 | 111 | if ( target.id == '' ) target.id = target.lat + ';' + target.lng; 112 | 113 | cfg.targets.push(target); 114 | }); 115 | 116 | return cfg; 117 | }, 118 | 119 | getRouteTime : function(travelOptions, successCallback, errorCallback) { 120 | 121 | var cfg = r360.TimeService.getCfg(travelOptions); 122 | 123 | if ( !r360.has(r360.TimeService.cache, JSON.stringify(cfg)) ) { 124 | 125 | // execute routing time service and call callback with results 126 | $.ajax({ 127 | url: travelOptions.getServiceUrl() + r360.config.serviceVersion + '/time?key=' +travelOptions.getServiceKey(), 128 | type: "POST", 129 | data: JSON.stringify(cfg) , 130 | contentType: "application/json", 131 | timeout: r360.config.requestTimeout, 132 | dataType: "json", 133 | success: function (result) { 134 | 135 | // the new version is an object, old one an array 136 | if ( r360.has(result, 'data') ) { 137 | 138 | if ( result.code == 'ok' ) { 139 | 140 | // cache the result 141 | r360.TimeService.cache[JSON.stringify(cfg)] = result.data; 142 | // call successCallback with returned results 143 | successCallback(result.data); 144 | } 145 | else 146 | // check if the error callback is defined 147 | if ( r360.isFunction(errorCallback) ) 148 | errorCallback(result.code, result.message); 149 | } 150 | // fallback for old clients 151 | else { 152 | 153 | // cache the result 154 | r360.TimeService.cache[JSON.stringify(cfg)] = result; 155 | // call successCallback with returned results 156 | successCallback(result); 157 | } 158 | }, 159 | // this only happens if the service is not available, all other errors have to be transmitted in the response 160 | error: function(data){ 161 | 162 | // call error callback if defined 163 | if ( r360.isFunction(errorCallback) ) { 164 | 165 | if ( data.status == 403 ) 166 | errorCallback("not-authorized", data.responseText); 167 | else 168 | errorCallback("service-not-available", "The time service is currently not available, please try again later."); 169 | } 170 | } 171 | }); 172 | } 173 | else { 174 | 175 | // call callback with returned results 176 | successCallback(r360.TimeService.cache[JSON.stringify(cfg)]); 177 | } 178 | } 179 | }; 180 | -------------------------------------------------------------------------------- /src/util/Browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.Browser handles different browser and feature detections for internal Leaflet use. 3 | */ 4 | 5 | (function () { 6 | 7 | var ua = navigator.userAgent.toLowerCase(), 8 | doc = document.documentElement, 9 | 10 | ie = 'ActiveXObject' in window, 11 | 12 | webkit = ua.indexOf('webkit') !== -1, 13 | phantomjs = ua.indexOf('phantom') !== -1, 14 | android23 = ua.search('android [23]') !== -1, 15 | chrome = ua.indexOf('chrome') !== -1, 16 | gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie, 17 | 18 | mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1, 19 | msPointer = navigator.msPointerEnabled && navigator.msMaxTouchPoints && !window.PointerEvent, 20 | pointer = (window.PointerEvent && navigator.pointerEnabled && navigator.maxTouchPoints) || msPointer, 21 | 22 | ie3d = ie && ('transition' in doc.style), 23 | webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, 24 | gecko3d = 'MozPerspective' in doc.style, 25 | opera12 = 'OTransition' in doc.style; 26 | 27 | var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window || 28 | (window.DocumentTouch && document instanceof window.DocumentTouch)); 29 | 30 | r360.Browser = { 31 | ie: ie, 32 | ielt9: ie && !document.addEventListener, 33 | webkit: webkit, 34 | gecko: gecko, 35 | android: ua.indexOf('android') !== -1, 36 | android23: android23, 37 | chrome: chrome, 38 | safari: !chrome && ua.indexOf('safari') !== -1, 39 | 40 | ie3d: ie3d, 41 | webkit3d: webkit3d, 42 | gecko3d: gecko3d, 43 | opera12: opera12, 44 | any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs, 45 | 46 | mobile: mobile, 47 | mobileWebkit: mobile && webkit, 48 | mobileWebkit3d: mobile && webkit3d, 49 | mobileOpera: mobile && window.opera, 50 | mobileGecko: mobile && gecko, 51 | 52 | touch: !!touch, 53 | msPointer: !!msPointer, 54 | pointer: !!pointer, 55 | 56 | retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1 57 | }; 58 | 59 | }()); 60 | -------------------------------------------------------------------------------- /src/util/Class.js: -------------------------------------------------------------------------------- 1 | /* 2 | * r360.Class powers the OOP facilities of the library. 3 | * Thanks to John Resig and Dean Edwards for inspiration! 4 | */ 5 | 6 | r360.Class = function () {}; 7 | 8 | r360.Class.extend = function (props) { 9 | 10 | // extended class with the new prototype 11 | var NewClass = function () { 12 | 13 | // call the constructor 14 | if (this.initialize) { 15 | this.initialize.apply(this, arguments); 16 | } 17 | 18 | // call all constructor hooks 19 | this.callInitHooks(); 20 | }; 21 | 22 | var parentProto = NewClass.__super__ = this.prototype; 23 | 24 | var proto = r360.Util.create(parentProto); 25 | proto.constructor = NewClass; 26 | 27 | NewClass.prototype = proto; 28 | 29 | // inherit parent's statics 30 | for (var i in this) { 31 | if (this.hasOwnProperty(i) && i !== 'prototype') { 32 | NewClass[i] = this[i]; 33 | } 34 | } 35 | 36 | // mix static properties into the class 37 | if (props.statics) { 38 | r360.extend(NewClass, props.statics); 39 | delete props.statics; 40 | } 41 | 42 | // mix includes into the prototype 43 | if (props.includes) { 44 | r360.Util.extend.apply(null, [proto].concat(props.includes)); 45 | delete props.includes; 46 | } 47 | 48 | // merge options 49 | if (proto.options) { 50 | props.options = r360.Util.extend(r360.Util.create(proto.options), props.options); 51 | } 52 | 53 | // mix given properties into the prototype 54 | r360.extend(proto, props); 55 | 56 | proto._initHooks = []; 57 | 58 | // add method for calling all hooks 59 | proto.callInitHooks = function () { 60 | 61 | if (this._initHooksCalled) { return; } 62 | 63 | if (parentProto.callInitHooks) { 64 | parentProto.callInitHooks.call(this); 65 | } 66 | 67 | this._initHooksCalled = true; 68 | 69 | for (var i = 0, len = proto._initHooks.length; i < len; i++) { 70 | proto._initHooks[i].call(this); 71 | } 72 | }; 73 | 74 | return NewClass; 75 | }; 76 | 77 | 78 | // method for adding properties to prototype 79 | r360.Class.include = function (props) { 80 | r360.extend(this.prototype, props); 81 | }; 82 | 83 | // merge new default options to the Class 84 | r360.Class.mergeOptions = function (options) { 85 | r360.extend(this.prototype.options, options); 86 | }; 87 | 88 | // add a constructor hook 89 | r360.Class.addInitHook = function (fn) { // (Function) || (String, args...) 90 | var args = Array.prototype.slice.call(arguments, 1); 91 | 92 | var init = typeof fn === 'function' ? fn : function () { 93 | this[fn].apply(this, args); 94 | }; 95 | 96 | this.prototype._initHooks = this.prototype._initHooks || []; 97 | this.prototype._initHooks.push(init); 98 | }; 99 | -------------------------------------------------------------------------------- /src/util/DomUtil.js: -------------------------------------------------------------------------------- 1 | r360.DomUtil = { 2 | 3 | setPosition: function (el, point) { // (HTMLElement, Point[, Boolean]) 4 | 5 | if (r360.Browser.any3d) { 6 | r360.DomUtil.setTransform(el, point); 7 | } else { 8 | el.style.left = point.x + 'px'; 9 | el.style.top = point.y + 'px'; 10 | } 11 | }, 12 | 13 | setTransform: function (el, offset, scale) { 14 | var pos = offset || new r360.Point(0, 0); 15 | 16 | el.style[r360.DomUtil.TRANSFORM] = 17 | 'translate3d(' + pos.x + 'px,' + pos.y + 'px' + ',0)' + (scale ? ' scale(' + scale + ')' : ''); 18 | }, 19 | 20 | testProp: function (props) { 21 | 22 | var style = document.documentElement.style; 23 | 24 | for (var i = 0; i < props.length; i++) { 25 | if (props[i] in style) { 26 | return props[i]; 27 | } 28 | } 29 | return false; 30 | } 31 | }; 32 | 33 | (function () { 34 | // prefix style property names 35 | r360.DomUtil.TRANSFORM = r360.DomUtil.testProp( 36 | ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); 37 | })(); 38 | -------------------------------------------------------------------------------- /src/util/PolygonUtil.js: -------------------------------------------------------------------------------- 1 | r360.PolygonUtil = { 2 | 3 | /** 4 | * [clip clipping like sutherland http://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#JavaScript] 5 | * @param {type} subjectPolygon [description] 6 | * @param {type} clipPolygon [description] 7 | * @return {type} [description] 8 | */ 9 | clip: function(subjectPolygon, clipPolygon) { 10 | 11 | var cp1, cp2, s, e; 12 | var inside = function (p) { 13 | return (cp2[0]-cp1[0])*(p[1]-cp1[1]) > (cp2[1]-cp1[1])*(p[0]-cp1[0]); 14 | }; 15 | var intersection = function () { 16 | var dc = [ cp1[0] - cp2[0], cp1[1] - cp2[1] ], 17 | dp = [ s[0] - e[0], s[1] - e[1] ], 18 | n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0], 19 | n2 = s[0] * e[1] - s[1] * e[0], 20 | n3 = 1.0 / (dc[0] * dp[1] - dc[1] * dp[0]); 21 | return [(n1*dp[0] - n2*dc[0]) * n3, (n1*dp[1] - n2*dc[1]) * n3]; 22 | }; 23 | var outputList = subjectPolygon; 24 | var cp1 = clipPolygon[clipPolygon.length-1]; 25 | for (j in clipPolygon) { 26 | var cp2 = clipPolygon[j]; 27 | var inputList = outputList; 28 | outputList = []; 29 | s = inputList[inputList.length - 1]; //last on the input list 30 | for (i in inputList) { 31 | var e = inputList[i]; 32 | if (inside(e)) { 33 | if (!inside(s)) { 34 | outputList.push(intersection()); 35 | } 36 | outputList.push(e); 37 | } 38 | else if (inside(s)) { 39 | outputList.push(intersection()); 40 | } 41 | s = e; 42 | } 43 | cp1 = cp2; 44 | } 45 | return outputList 46 | }, 47 | 48 | /** 49 | * [isCollinear Checks if the given three points are collinear. Also see 50 | * https://en.wikipedia.org/wiki/Collinearity. This method uses a tolerance 51 | * factor defined in r360.config.defaultPolygonLayerOptions.tolerance.] 52 | * 53 | * @param {type} p1 [description] 54 | * @param {type} p2 [description] 55 | * @param {type} p3 [description] 56 | * @return {Boolean} [description] 57 | */ 58 | isCollinear: function(p1, p2, p3){ 59 | 60 | if(p1.x == p3.x && p1.y == p3.y) 61 | return false; 62 | if(p1.x == p2.x && p2.x == p3.x) 63 | return true; 64 | if(p1.y == p2.y && p2.y == p3.y) 65 | return true; 66 | 67 | var val = (p1.x * (p2.y -p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)); 68 | 69 | if ( val < r360.config.defaultPolygonLayerOptions.tolerance && 70 | val > -r360.config.defaultPolygonLayerOptions.tolerance && 71 | p1.x != p3.x && p1.y != p3.y ) 72 | return true; 73 | 74 | return false; 75 | }, 76 | 77 | /** 78 | * [scale Scales a point (x and y coordinate) by the given scale. This method changes 79 | * the values of the given point.] 80 | * @param {type} point [the point to be scaled] 81 | * @param {type} scale [the scale] 82 | * @return {type} [the scaled point] 83 | */ 84 | scale: function(point, scale){ 85 | return r360.point(point.x * scale, point.y * scale); 86 | }, 87 | 88 | /** 89 | * [subtract Subtracts the given x and y coordinate from the cooresponding values of the given point. 90 | * This method changes the values of the given point. ] 91 | * @param {type} point [the point to be changed] 92 | * @param {type} x [the x value to be subtracted] 93 | * @param {type} y [the y value to be subtracted] 94 | * @return {type} [the subtracted point] 95 | */ 96 | subtract: function(point, x, y){ 97 | return r360.point(point.x - x, point.y - y); 98 | }, 99 | 100 | divide: function(point, quotient){ 101 | return r360.point(point.x / quotient, point.y / quotient); 102 | }, 103 | 104 | /** 105 | * [roundPoint Rounds a point's x and y coordinate. The method changes the x and y 106 | * values of the given point. If the fractional portion of number (x and y) 107 | * is 0.5 or greater, the argument is rounded to the next higher integer. If the 108 | * fractional portion of number is less than 0.5, the argument is rounded to the 109 | * next lower integer.] 110 | * 111 | * @param {type} point [the point to rounded] 112 | * @return {type} [the point to be rounded with integer x and y coordinate] 113 | */ 114 | roundPoint: function(point){ 115 | point.x = Math.round(point.x); 116 | point.y = Math.round(point.y); 117 | return point; 118 | }, 119 | 120 | /** 121 | * [buildPath Creates an SVG path. ] 122 | * @param {type} point [The point to add] 123 | * @param {type} suffix [The svg suffix for the point] 124 | * @return {type} [An array containing the suffix, point.x, point.y] 125 | */ 126 | buildPath:function(point, suffix){ 127 | 128 | return [suffix, Math.round(point.x), Math.round(point.y)]; 129 | }, 130 | 131 | /** 132 | * [getEuclidianDistance This method returns the euclidean distance between two points (x and y coordinates).] 133 | * @param {type} point1 [the first point] 134 | * @param {type} point2 [the second point] 135 | * @return {type} [the distance] 136 | */ 137 | getEuclidianDistance: function(point1, point2){ 138 | return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)); 139 | }, 140 | 141 | /** 142 | * [getSvgFrame description] 143 | * @param {type} width [description] 144 | * @param {type} height [description] 145 | * @return {type} [description] 146 | */ 147 | getSvgFrame: function(width, height){ 148 | return [['M',0, 0], ['L',width, 0], ['L',width, height], ['L',0, height], ['z']]; 149 | }, 150 | 151 | /** 152 | * [extendBounds description] 153 | * @param {type} bounds [description] 154 | * @param {type} extendWidthX [description] 155 | * @param {type} extendWidthY [description] 156 | * @return {type} [description] 157 | */ 158 | extendBounds : function(bounds, extendWidthX, extendWidthY) { 159 | 160 | var extendX = Math.ceil(extendWidthX); 161 | var extendY = Math.ceil(extendWidthY); 162 | 163 | bounds.max.x += extendX; 164 | bounds.min.x -= extendX; 165 | bounds.max.y += extendY; 166 | bounds.min.y -= extendY; 167 | 168 | return bounds; 169 | }, 170 | 171 | /* 172 | * 173 | */ 174 | addPolygonToMultiPolygon: function(multiPolygons, polygon){ 175 | 176 | var filteredMultiPolygons = r360.filter(multiPolygons, function(multiPolygon){ return multiPolygon.getTravelTime() == polygon.travelTime; }); 177 | 178 | // multipolygon with polygon's travetime already there 179 | if ( filteredMultiPolygons.length > 0 ) filteredMultiPolygons[0].addPolygon(polygon); 180 | else { 181 | 182 | var multiPolygon = new r360.multiPolygon(); 183 | multiPolygon.setTravelTime(polygon.travelTime); 184 | multiPolygon.addPolygon(polygon); 185 | multiPolygon.setColor(polygon.getColor()); 186 | multiPolygon.setOpacity(polygon.getOpacity()); 187 | multiPolygons.push(multiPolygon); 188 | } 189 | }, 190 | } -------------------------------------------------------------------------------- /src/util/SvgUtil.js: -------------------------------------------------------------------------------- 1 | r360.SvgUtil = { 2 | 3 | /** 4 | * [getGElement description] 5 | * @param {type} svgData [description] 6 | * @param {type} opacity [description] 7 | * @param {type} color [description] 8 | * @param {type} animate [description] 9 | * @return {type} [description] 10 | */ 11 | getGElement : function(svgData, options){ 12 | 13 | var randomId = r360.Util.generateId(); 14 | var initialOpacity = options.opacity; 15 | 16 | return ""+ 17 | ""+ 18 | ""; 19 | }, 20 | 21 | /** 22 | * [getInverseSvgElement description] 23 | * @param {type} gElements [description] 24 | * @return {type} [description] 25 | */ 26 | getInverseSvgElement: function(gElements, options){ 27 | 28 | var svgFrame = r360.PolygonUtil.getSvgFrame(options.svgWidth, options.svgHeight); 29 | 30 | var svgStart = "
" 34 | var svgEnd = "
"; 35 | 36 | var newSvg = ""+ 37 | ""+ 38 | ""+ 39 | gElements.join('') + 40 | ""+ 41 | ""; 42 | 43 | var frame = ""; 44 | 45 | return svgStart + frame + newSvg + svgEnd; 46 | }, 47 | 48 | /** 49 | * [getNormalSvgElement description] 50 | * @param {type} gElement [description] 51 | * @return {type} [description] 52 | */ 53 | getNormalSvgElement: function(gElements, options){ 54 | 55 | var svgStart = "
" 59 | var svgEnd = "
"; 60 | 61 | return svgStart + gElements.join('') + svgEnd; 62 | }, 63 | 64 | /** 65 | * [createSvgData description] 66 | * @param {type} polygon [description] 67 | * @param {type} options [description] 68 | * @return {type} [description] 69 | */ 70 | createSvgData : function(polygon, options) { 71 | 72 | var pathData = []; 73 | 74 | var topRight = r360.PolygonUtil.scale(polygon.getTopRightDecimal(), options.scale); 75 | var bottomLeft = r360.PolygonUtil.scale(polygon.getBottomLeftDecimal(), options.scale); 76 | 77 | // the outer boundary 78 | if ( !(bottomLeft.x > options.bounds.max.x || topRight.x < options.bounds.min.x || 79 | topRight.y > options.bounds.max.y || bottomLeft.y < options.bounds.min.y )) 80 | r360.SvgUtil.buildSVGPolygon(pathData, polygon.getOuterBoundary().getCoordinates(), options); 81 | 82 | var innerBoundary = polygon.getInnerBoundary(); 83 | 84 | // the inner boundaries 85 | for ( var i = 0 ; i < innerBoundary.length ; i++ ) { 86 | 87 | var topRightInner = r360.PolygonUtil.scale(innerBoundary[i].getTopRightDecimal(), options.scale); 88 | var bottomLeftInner = r360.PolygonUtil.scale(innerBoundary[i].getBottomLeftDecimal(), options.scale); 89 | 90 | if ( !(bottomLeftInner.x > options.bounds.max.x || topRightInner.x < options.bounds.min.x || 91 | topRightInner.y > options.bounds.max.y || bottomLeftInner.y < options.bounds.min.y )) 92 | r360.SvgUtil.buildSVGPolygon(pathData, innerBoundary[i].getCoordinates(), options); 93 | } 94 | 95 | return pathData; 96 | }, 97 | 98 | /** 99 | * [buildSVGPolygon description] 100 | * @param {type} pathData [description] 101 | * @param {type} coordinateArray [description] 102 | * @param {type} bounds [description] 103 | * @param {type} scale [description] 104 | * @return {type} [description] 105 | */ 106 | buildSVGPolygon: function(pathData, coordinateArray, options){ 107 | 108 | var point, point1, point2, isCollinear, euclidianDistance, pointCount = 0; 109 | var boundArray = [[options.bounds.min.x, options.bounds.min.y], 110 | [options.bounds.max.x, options.bounds.min.y], 111 | [options.bounds.max.x, options.bounds.max.y], 112 | [options.bounds.min.x, options.bounds.max.y]]; 113 | 114 | var pointsToClip = []; 115 | 116 | for ( var i = 0 ; i < coordinateArray.length ; i++ ) { 117 | 118 | point = r360.PolygonUtil.scale(r360.point(coordinateArray[i].x, coordinateArray[i].y), options.scale); 119 | 120 | euclidianDistance = (i > 0) ? r360.PolygonUtil.getEuclidianDistance(point2, point) : options.tolerance; 121 | 122 | if ( euclidianDistance >= options.tolerance ) { 123 | 124 | isCollinear = false; 125 | 126 | if ( pointCount > 2 ) 127 | isCollinear = r360.PolygonUtil.isCollinear(point1, point2, point); 128 | 129 | if ( isCollinear ) { 130 | pointsToClip[pointsToClip.length-1][0] = point.x; 131 | pointsToClip[pointsToClip.length-1][1] = point.y; 132 | } 133 | else { 134 | 135 | pointsToClip.push([point.x, point.y]); 136 | point1 = point2; 137 | point2 = point; 138 | pointCount++; 139 | } 140 | } 141 | } 142 | 143 | var clippedArray = r360.PolygonUtil.clip(pointsToClip, boundArray); 144 | var lastPoint; 145 | 146 | for ( var i = 0 ; i < clippedArray.length ; i++ ){ 147 | 148 | point = r360.PolygonUtil.subtract(r360.point(clippedArray[i][0], clippedArray[i][1]), 149 | options.pixelOrigin.x + options.offset.x, 150 | options.pixelOrigin.y + options.offset.y) 151 | 152 | pathData.push( i > 0 ? r360.PolygonUtil.buildPath(point, "L") : r360.PolygonUtil.buildPath(point, "M")); 153 | lastPoint = point; 154 | } 155 | 156 | if ( pathData.length > 0 ) 157 | pathData.push(["z"]); // svgz 158 | 159 | return pathData; 160 | }, 161 | } -------------------------------------------------------------------------------- /src/util/TravelOptions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.TravelOptions = function(){ 5 | 6 | this.sources = []; 7 | this.targets = []; 8 | 9 | this.bikeSpeed = undefined; 10 | this.bikeUphill = undefined; 11 | this.bikeDownhill = undefined; 12 | this.walkSpeed = undefined; 13 | this.walkUphill = undefined; 14 | this.walkDownhill = undefined; 15 | 16 | this.travelTimes = undefined; 17 | this.travelType = undefined; 18 | this.elevationEnabled = undefined; 19 | this.rushHour = undefined; 20 | 21 | this.minPolygonHoleSize = undefined; 22 | this.buffer = undefined; 23 | this.simplify = undefined; 24 | this.srid = undefined; 25 | this.quadrantSegments = undefined; 26 | 27 | this.time = undefined; 28 | this.date = undefined; 29 | this.frameDuration = undefined; 30 | this.reverse = undefined; 31 | this.recommendations = undefined; 32 | 33 | this.intersectionMode = undefined; 34 | this.pathSerializer = 'compact'; 35 | this.polygonSerializer = 'json'; 36 | this.pointReduction = true; 37 | this.maxRoutingTime = undefined; // Deprecated 38 | this.maxRoutingLength = undefined; // Deprecated 39 | this.maxEdgeWeight = undefined; 40 | this.edgeWeight = 'time'; 41 | this.serviceUrl = undefined; 42 | this.serviceKey = undefined; 43 | 44 | this.travelTimeFactors = undefined; 45 | this.maxTransfers = undefined; 46 | 47 | this.getReverse = function(){ return this.reverse; } 48 | this.setReverse = function(reverse){ this.reverse = reverse; } 49 | 50 | this.getFrameDuration = function(){ return this.frameDuration; } 51 | this.setFrameDuration = function(frameDuration){ this.frameDuration = frameDuration; } 52 | 53 | 54 | this.getBuffer = function(){ return this.buffer; } 55 | 56 | /** 57 | * @description Set the buffer to apply to the polygons. Buffer is in units as defined by the srid. For WGS84 (lat/long, srid: 4326) the unit is degrees. The length of a degree varies depending on location, and specifically for longitute, which converges at the poles. You may want to [calculate](http://msi.nga.mil/MSISiteContent/StaticFiles/Calculators/degree.html) the buffer in degrees based on location, if using WGS84. 58 | * For 59 | * 60 | * @param {long} buffer - The polygon's buffer width (in srid units). 61 | */ 62 | this.setBuffer = function(buffer){ this.buffer = buffer; } 63 | 64 | this.getSimplifyMeter = function(){ return this.simplify; } 65 | this.setSimplifyMeter = function(simplify){ this.simplify = simplify; } 66 | 67 | this.getSrid = function(){ return this.srid; } 68 | this.setSrid = function(srid){ this.srid = srid; } 69 | 70 | this.getQuadrantSegments = function(){ return this.quadrantSegments; } 71 | this.setQuadrantSegments = function(quadrantSegments){ this.quadrantSegments = quadrantSegments; } 72 | 73 | /* 74 | * 75 | * 76 | * 77 | */ 78 | this.getSources = function(){ 79 | 80 | return this.sources; 81 | } 82 | 83 | /* 84 | * 85 | * 86 | * 87 | */ 88 | this.addSource = function(source){ 89 | 90 | this.sources.push(source); 91 | } 92 | 93 | 94 | 95 | /* 96 | * 97 | * 98 | * 99 | */ 100 | this.addTarget = function(target){ 101 | 102 | this.targets.push(target); 103 | } 104 | 105 | /* 106 | * 107 | * 108 | * 109 | */ 110 | this.getTargets = function(){ 111 | 112 | return this.targets; 113 | } 114 | 115 | /* 116 | * 117 | * 118 | * 119 | */ 120 | this.getBikeSpeed = function(){ 121 | 122 | return this.bikeSpeed; 123 | } 124 | 125 | /* 126 | * 127 | * 128 | * 129 | */ 130 | this.getBikeUphill = function(){ 131 | 132 | return this.bikeUphill; 133 | } 134 | 135 | /* 136 | * 137 | * 138 | * 139 | */ 140 | this.getBikeDownhill = function(){ 141 | 142 | return this.bikeDownhill; 143 | } 144 | 145 | /* 146 | * 147 | * 148 | * 149 | */ 150 | this.getWalkSpeed = function(){ 151 | 152 | return this.walkSpeed; 153 | } 154 | 155 | /* 156 | * 157 | * 158 | * 159 | */ 160 | this.getWalkUphill = function(){ 161 | 162 | return this.walkUphill; 163 | } 164 | 165 | /* 166 | * 167 | * 168 | * 169 | */ 170 | this.getWalkDownhill = function(){ 171 | 172 | return this.walkDownhill; 173 | } 174 | 175 | /* 176 | * 177 | * 178 | * 179 | */ 180 | this.getTravelTimes = function(){ 181 | 182 | return this.travelTimes; 183 | } 184 | 185 | /* 186 | * 187 | * 188 | * 189 | */ 190 | this.getTravelType = function(){ 191 | 192 | return this.travelType; 193 | } 194 | 195 | /* 196 | * 197 | * 198 | * 199 | */ 200 | this.getTime = function(){ 201 | 202 | return this.time; 203 | } 204 | 205 | /* 206 | * 207 | * 208 | * 209 | */ 210 | this.getDate = function(){ 211 | 212 | return this.date; 213 | } 214 | 215 | 216 | /* 217 | * 218 | * 219 | * 220 | */ 221 | this.getPathSerializer = function(){ 222 | 223 | return this.pathSerializer; 224 | } 225 | 226 | /** 227 | * [getPolygonSerializer description] 228 | * @return {type} [description] 229 | */ 230 | this.getPolygonSerializer = function(){ 231 | 232 | return this.polygonSerializer; 233 | } 234 | 235 | /* 236 | * 237 | * 238 | * 239 | */ 240 | this.getMaxRoutingTime = function(){ 241 | 242 | console.warn("getMaxRoutingTime is deprecated. use getMaxEdgeWeight instead") 243 | return this.maxRoutingTime; 244 | } 245 | 246 | /* 247 | * 248 | * 249 | * 250 | */ 251 | this.getMaxRoutingLength = function(){ 252 | console.warn("getMaxRoutingLength is deprecated. use getMaxEdgeWeight instead") 253 | return this.maxRoutingLength; 254 | } 255 | 256 | this.getMaxEdgeWeight = function(){ 257 | return this.maxEdgeWeight; 258 | } 259 | 260 | /* 261 | * 262 | * 263 | * 264 | */ 265 | this.getIntersectionMode = function(){ 266 | 267 | return this.intersectionMode; 268 | } 269 | 270 | /* 271 | * 272 | * 273 | * 274 | */ 275 | this.getRecommendations = function(){ 276 | 277 | return this.recommendations; 278 | } 279 | 280 | /* 281 | * 282 | * 283 | * 284 | */ 285 | this.getServiceUrl = function(){ 286 | 287 | return this.serviceUrl; 288 | } 289 | 290 | /* 291 | * 292 | * 293 | * 294 | */ 295 | this.getServiceKey = function(){ 296 | 297 | return this.serviceKey; 298 | } 299 | 300 | /* 301 | * 302 | * 303 | * 304 | */ 305 | this.setServiceKey = function(serviceKey){ 306 | 307 | this.serviceKey = serviceKey; 308 | } 309 | 310 | /* 311 | * 312 | * 313 | * 314 | */ 315 | this.setServiceUrl = function(serviceUrl){ 316 | 317 | this.serviceUrl = serviceUrl; 318 | } 319 | 320 | /* 321 | * 322 | * 323 | * 324 | */ 325 | this.setRecommendations = function(recommendations){ 326 | 327 | this.recommendations = recommendations; 328 | } 329 | 330 | /* 331 | * 332 | * 333 | * 334 | */ 335 | this.setIntersectionMode = function(intersectionMode){ 336 | 337 | this.intersectionMode = intersectionMode; 338 | } 339 | 340 | /* 341 | * 342 | * 343 | * 344 | */ 345 | this.setMaxRoutingTime = function(maxRoutingTime){ 346 | 347 | console.warn("setMaxRoutingTime is deprecated. use setMaxEdgeWeight instead") 348 | this.maxRoutingTime = maxRoutingTime; 349 | } 350 | 351 | /* 352 | * 353 | * 354 | * 355 | */ 356 | this.setMaxRoutingLength = function(maxRoutingLength){ 357 | console.warn("setMaxRoutingLength is deprecated. use setMaxEdgeWeight instead") 358 | this.maxRoutingLength = maxRoutingLength; 359 | } 360 | 361 | 362 | this.setMaxEdgeWeight = function(maxEdgeWeight){ 363 | this.maxEdgeWeight = maxEdgeWeight; 364 | } 365 | 366 | /* 367 | * 368 | * 369 | * 370 | */ 371 | this.setPathSerializer = function(pathSerializer){ 372 | 373 | this.pathSerializer = pathSerializer; 374 | } 375 | 376 | this.setPolygonSerializer = function(polygonSerializer){ 377 | 378 | this.polygonSerializer = polygonSerializer; 379 | } 380 | 381 | 382 | /** 383 | * [setMinPolygonHoleSize description] 384 | * @param {type} minPolygonHoleSize [description] 385 | */ 386 | this.setMinPolygonHoleSize = function(minPolygonHoleSize){ 387 | 388 | this.minPolygonHoleSize = minPolygonHoleSize; 389 | } 390 | 391 | /** 392 | * [getMinPolygonHoleSize description] 393 | * @return {type} [description] 394 | */ 395 | this.getMinPolygonHoleSize = function(){ 396 | 397 | return this.minPolygonHoleSize; 398 | } 399 | 400 | /* 401 | * 402 | * 403 | * 404 | */ 405 | this.setSources = function(sources){ 406 | 407 | this.sources = sources; 408 | } 409 | 410 | /* 411 | * 412 | * 413 | * 414 | */ 415 | this.setTargets = function(targets){ 416 | 417 | this.targets = targets; 418 | } 419 | 420 | /* 421 | * 422 | * 423 | * 424 | */ 425 | this.setBikeSpeed = function(bikeSpeed){ 426 | 427 | this.bikeSpeed = bikeSpeed; 428 | } 429 | 430 | /* 431 | * 432 | * 433 | * 434 | */ 435 | this.setBikeUphill = function(bikeUphill){ 436 | 437 | this.bikeUphill = bikeUphill; 438 | } 439 | 440 | /* 441 | * 442 | * 443 | * 444 | */ 445 | this.setBikeDownhill = function(bikeDownhill){ 446 | 447 | this.bikeDownhill = bikeDownhill; 448 | } 449 | 450 | /* 451 | * 452 | * 453 | * 454 | */ 455 | this.setWalkSpeed = function(walkSpeed){ 456 | 457 | this.walkSpeed = walkSpeed; 458 | } 459 | 460 | /* 461 | * 462 | * 463 | * 464 | */ 465 | this.setWalkUphill = function(walkUphill){ 466 | 467 | this.walkUphill = walkUphill; 468 | } 469 | 470 | /* 471 | * 472 | * 473 | * 474 | */ 475 | this.setWalkDownhill = function(walkDownhill){ 476 | 477 | this.walkDownhill = walkDownhill; 478 | } 479 | 480 | /* 481 | * 482 | * 483 | * 484 | */ 485 | this.setTravelTimes = function(travelTimes){ 486 | 487 | this.travelTimes = travelTimes; 488 | } 489 | 490 | /* 491 | * 492 | * 493 | * 494 | */ 495 | this.setTravelType = function(travelType){ 496 | 497 | this.travelType = travelType; 498 | } 499 | 500 | /* 501 | * 502 | * 503 | * 504 | */ 505 | this.setTime = function(time){ 506 | 507 | this.time = time; 508 | } 509 | 510 | /* 511 | * 512 | * 513 | * 514 | */ 515 | this.setDate = function(date){ 516 | 517 | this.date = date; 518 | } 519 | 520 | /** 521 | * [isElevationEnabled if true the service will return elevation data, if the backend is 522 | * configured with elevation data, if the backend is not configured with elevation data 523 | * the z value of all points in routes is 0] 524 | * 525 | * @return {boolean} [returns true if elevation enabled] 526 | */ 527 | this.isElevationEnabled = function() { 528 | 529 | return this.elevationEnabled; 530 | } 531 | 532 | /** 533 | * [setElevationEnabled if set to true the service will return elevation data, if the backend is 534 | * configured with elevation data, if the backend is not configured with elevation data 535 | * the z value of all points in routes is 0] 536 | * @param {type} elevationEnabled [set the backend to consider elevation data for polygonizing and routing] 537 | */ 538 | this.setElevationEnabled = function(elevationEnabled){ 539 | 540 | this.elevationEnabled = elevationEnabled; 541 | } 542 | 543 | /** 544 | * [isRushHour if true the service will return congested speed data, if the backend is 545 | * configured with rush hour data 546 | * 547 | * @return {boolean} [returns true if rush hour is enabled] 548 | */ 549 | this.isRushHour = function() { 550 | 551 | return this.rushHour; 552 | } 553 | 554 | this.setRushHour = function(rushHour){ 555 | 556 | this.rushHour = rushHour; 557 | } 558 | 559 | /** 560 | * @deprecated since v0.3.2. use isRushHour instead 561 | * 562 | * @return {boolean} [returns true if rush hour is enabled] 563 | */ 564 | this.isCongestionEnabled = function() { 565 | console.warn("Calling deprecated function \"isCongestionEnabled()\". Use \"isRushHour()\" instead."); 566 | return this.rushHour; 567 | } 568 | 569 | /** 570 | * @deprecated since v0.3.2. Use setRushHour instead 571 | */ 572 | this.setCongestionEnabled = function(rushHour){ 573 | console.warn("Calling deprecated function \"setCongestionEnabled()\". Use \"setRushHour()\" instead.") 574 | this.rushHour = rushHour; 575 | } 576 | 577 | this.disablePointReduction = function(){ 578 | this.pointReduction = false; 579 | } 580 | 581 | this.enablePointReduction = function(){ 582 | this.pointReduction = true; 583 | } 584 | 585 | this.isPointReductionEnabled = function(){ 586 | return this.pointReduction; 587 | } 588 | 589 | this.getEdgeWeight = function(){ 590 | return this.edgeWeight; 591 | } 592 | 593 | this.setEdgeWeight = function(edgeWeight){ 594 | this.edgeWeight = edgeWeight; 595 | } 596 | 597 | this.getTravelTimeFactors = function(){ 598 | return this.travelTimeFactors; 599 | } 600 | 601 | this.setTravelTimeFactors = function(travelTimeFactors){ 602 | this.travelTimeFactors = travelTimeFactors; 603 | } 604 | 605 | this.getMaxTransfers = function(){ 606 | return this.maxTransfers; 607 | } 608 | 609 | this.setMaxTransfers = function(maxTransfers){ 610 | this.maxTransfers = maxTransfers; 611 | } 612 | }; 613 | 614 | r360.travelOptions = function () { 615 | return new r360.TravelOptions(); 616 | }; 617 | -------------------------------------------------------------------------------- /src/util/Util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | r360.Util = { 5 | 6 | /* 7 | * This method returns the current time, at the time this method is executed, 8 | * in seconds. This means that the current hours, minutes and seconds of the current 9 | * time are added up, e.g.: 12:11:15 pm: 10 | * 11 | * -> (12 * 3600) + (11 * 60) + 15 = 43875 12 | * 13 | * @method getTimeInSeconds 14 | * 15 | * @returns {Number} The current time in seconds 16 | */ 17 | getTimeInSeconds : function() { 18 | 19 | var now = new Date(); 20 | return (now.getHours() * 3600) + (now.getMinutes() * 60) + now.getSeconds(); 21 | }, 22 | 23 | /* 24 | * This method returns the current time in seconds, rounded down to the nearest minute, 25 | * at the time this method is executed. This means that the current hours and minutes of the 26 | * current time are converted to seconds and added up, e.g.: 12:11 pm: 27 | * 28 | * -> (12 * 3600) + (11 * 60) = 43860 29 | * 30 | * @method getHoursAndMinutesInSeconds 31 | * 32 | * @returns {Number} The current time in seconds 33 | */ 34 | getHoursAndMinutesInSeconds : function() { 35 | 36 | var now = new Date(); 37 | return (now.getHours() * 3600) + (now.getMinutes() * 60); 38 | }, 39 | 40 | /* 41 | * Returns the current date in the form 20140508 (YYYYMMDD). Note that month is 42 | * not zero but 1 based, which means 6 == June. 43 | * 44 | * @method getCurrentDate 45 | * 46 | * @return {String} the date object in string representation YYYYMMDD 47 | */ 48 | getCurrentDate : function() { 49 | 50 | var date = new Date(); 51 | var year = date.getFullYear(); 52 | var month = (date.getMonth() + 1) < 10 ? "0" + (date.getMonth() + 1) : (date.getMonth() + 1); 53 | var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); 54 | 55 | return year + "" + month + "" + day; 56 | }, 57 | 58 | getTimeFormat : function(seconds) { 59 | 60 | var i18n = r360.config.i18n; 61 | if ( i18n.language == 'en' ) if ( seconds >= 43200 ) return 'p.m.'; 62 | return i18n.get('timeFormat'); 63 | }, 64 | 65 | /* 66 | * Transforms the given seconds to a hour and minuten view. This means 67 | * that for example 10:15 (one hour and 15 minutes) is translates to the string: 68 | * -> 10h 15min 69 | * 70 | * Note that no trailing zeros are returned. Also if hours < 1 only minute values will be returned. 71 | * 72 | * @method secondsToHoursAndMinutes 73 | * @returns {String} the transformed seconds in "xh ymin" 74 | */ 75 | secondsToHoursAndMinutes : function(seconds) { 76 | 77 | var minutes = (seconds / 60).toFixed(0); 78 | var hours = Math.floor(minutes / 60); 79 | 80 | minutes = minutes - hours * 60; 81 | var timeString = ""; 82 | 83 | if (hours != 0) timeString += (hours + "h "); 84 | timeString += (minutes + "min"); 85 | 86 | return timeString; 87 | }, 88 | 89 | /* 90 | * This methods transforms a given time in seconds to a format like: 91 | * 43200 -> 12:00:00 92 | * 93 | * @method secondsToTimeOfDay 94 | * @returns {String} the formated time string in the format HH:MM:ss 95 | */ 96 | secondsToTimeOfDay : function(seconds){ 97 | 98 | var hours = Math.floor(seconds/3600); 99 | var minutes = Math.floor(seconds/60)-hours*60; 100 | seconds = seconds - (hours * 3600) - (minutes *60); 101 | return hours+":"+ ("0" + minutes).slice(-2) +":"+ ("0" + seconds).slice(-2); 102 | }, 103 | 104 | /* 105 | * This methods generates a unique ID with the given length or 10 if no length was given. 106 | * The method uses all characters from [A-z0-9] but does not guarantuee a unique string. 107 | * It's more a pseudo random string. 108 | * 109 | * @method generateId 110 | * @param the length of the returned pseudo random string 111 | * @return a random string with the given length 112 | */ 113 | generateId : function(length) { 114 | 115 | var id = ""; 116 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 117 | 118 | for (var i = 0; i < (length ? length : 10); i++) { 119 | id += possible.charAt(Math.floor(Math.random() * possible.length)); 120 | } 121 | 122 | return id; 123 | }, 124 | 125 | /* 126 | * 127 | */ 128 | parseLatLonArray : function(latlngs) { 129 | 130 | var coordinates = new Array(); 131 | 132 | for ( var i = 0 ; i < latlngs.length ; i++ ) 133 | coordinates.push(new r360.Point(latlngs[i][0], latlngs[i][1])) 134 | 135 | return coordinates; 136 | }, 137 | 138 | /* 139 | * This methods uses the Rotue360° geocoding service to return 140 | * a street address for a given latitude/longitude coordinate pair. 141 | * This functionality is typically called reverse geocoding. 142 | * 143 | * @method getAddressByCoordinates 144 | * @param {Object} [latlon] The coordinate 145 | * @param {Number} [latlon.lat] The latitude of the coordinate. 146 | * @param {Number} [latlon.lng] The longitude of the coordinate. 147 | * @param {String} [language] The country code, 'nb' for norway, 'de' for germany. 148 | * @param {Function} [callback] The callback methods which processes the returned data. 149 | */ 150 | getAddressByCoordinates : function(latlng, language, callback){ 151 | 152 | $.getJSON(r360.config.nominatimUrl + 'reverse.php?&format=json&lat=' + latlng.lat + '&accept-language=' + language + '&lon=' + latlng.lng + '&json_callback=?', callback); 153 | }, 154 | 155 | /* 156 | * This method takes a result from the nominatim reverse geocoder and formats 157 | * it to a readable and displayable string. It builds up an address like this: 158 | * 'STREETNAME STREETNUMBER, POSTALCODE, CITY' 159 | * In case any of these values are undefined, they get removed from returned string. 160 | * In case all values are undefined, the 'display_name' property of the returned 161 | * json (from nominatim) is used to generate the output value. 162 | * @return {String} a string representing the geocoordinates in human readable form 163 | */ 164 | formatReverseGeocoding : function(json) { 165 | 166 | var streetAdress = []; 167 | if ( r360.has(json.address, 'road') ) streetAdress.push(json.address.road); 168 | if ( r360.has(json.address, 'house_number') ) streetAdress.push(json.address.house_number); 169 | 170 | var city = []; 171 | if ( r360.has(json.address, 'postcode') ) city.push(json.address.postcode); 172 | if ( r360.has(json.address, 'city') ) city.push(json.address.city); 173 | 174 | var address = []; 175 | if ( streetAdress.length > 0 ) address.push(streetAdress.join(' ')); 176 | if ( city.length > 0) address.push(city.join(', ')); 177 | 178 | if ( streetAdress.length == 0 && city.length == 0 ) address.push(json.display_name); 179 | 180 | return address.join(', '); 181 | }, 182 | 183 | /** 184 | * [formatPhotonReverseGeocoding description] 185 | * @param {type} place [description] 186 | * @return {type} [description] 187 | */ 188 | formatPhotonReverseGeocoding : function(place) { 189 | 190 | var streetAdress = []; 191 | if ( r360.has(place, 'name') ) streetAdress.push(place.name); 192 | if ( r360.has(place, 'street') ) streetAdress.push(place.street); 193 | if ( r360.has(place, 'housenumber') ) streetAdress.push(place.housenumber); 194 | 195 | var city = []; 196 | if ( r360.has(place, 'postcode') ) city.push(place.postcode); 197 | if ( r360.has(place, 'city') ) city.push(place.city); 198 | 199 | var address = []; 200 | if ( streetAdress.length > 0 ) address.push(streetAdress.join(' ')); 201 | if ( city.length > 0) address.push(city.join(', ')); 202 | 203 | if ( streetAdress.length == 0 && city.length == 0 ) address.push("Reverse geocoding not possible."); 204 | 205 | return address.join(', '); 206 | }, 207 | 208 | /* 209 | * 210 | */ 211 | parsePolygons : function(polygonsJson) { 212 | 213 | var multiPolygon = []; 214 | 215 | // we get polygons for each source 216 | for ( var i = 0 ; i < polygonsJson.length ; i++ ) { 217 | 218 | var source = polygonsJson[i]; 219 | 220 | for ( var j = 0; j < source.polygons.length; j++ ) { 221 | 222 | // get the polygon infos 223 | var polygonJson = source.polygons[j]; 224 | // create a polygon with the outer boundary as the initial linestring 225 | var polygon = r360.polygon(polygonJson.travelTime, polygonJson.area, r360.lineString(r360.Util.parseLatLonArray(polygonJson.outerBoundary))); 226 | // set color and default to black of not found 227 | var color = r360.findWhere(r360.config.defaultTravelTimeControlOptions.travelTimes, { time : polygon.getTravelTime() }); 228 | polygon.setColor(!r360.isUndefined(color) ? color.color : '#000000'); 229 | // set opacity and default to 1 if not found 230 | var opacity = r360.findWhere(r360.config.defaultTravelTimeControlOptions.travelTimes, { time : polygon.getTravelTime() }) 231 | polygon.setOpacity(!r360.isUndefined(opacity) ? opacity.opacity : 1); 232 | 233 | if ( typeof polygonJson.innerBoundary !== 'undefined' ) { 234 | 235 | // add all inner linestrings to polygon 236 | for ( var k = 0 ; k < polygonJson.innerBoundary.length ; k++ ) 237 | polygon.addInnerBoundary(r360.lineString(r360.Util.parseLatLonArray(polygonJson.innerBoundary[k]))); 238 | } 239 | 240 | r360.PolygonUtil.addPolygonToMultiPolygon(multiPolygon, polygon); 241 | } 242 | } 243 | 244 | // make sure the multipolygons are sorted by the travel time ascendingly 245 | multiPolygon.sort(function(a,b) { return b.getTravelTime() - a.getTravelTime(); }); 246 | 247 | return multiPolygon; 248 | }, 249 | 250 | /* 251 | * This method parses the JSON returned from the Route360 Webservice and generates 252 | * java script objects representing the values. 253 | */ 254 | parseRoutes : function(json){ 255 | 256 | var routes = new Array(); 257 | 258 | for(var i = 0; i < json.routes.length; i++){ 259 | var meta = json.routes[i]; 260 | routes.push(r360.route(json.routes[i].travelTime, json.routes[i].segments, meta)); 261 | } 262 | 263 | return routes; 264 | }, 265 | // 3857 => pixel 266 | webMercatorToLeaflet : function(point){ 267 | return r360.CRS.EPSG3857.transformation._transform(r360.point(point.x / 6378137, point.y / 6378137)); 268 | }, 269 | 270 | webMercatorToLatLng : function(point, elevation){ 271 | 272 | var latlng = r360.CRS.EPSG3857.projection.unproject(new r360.Point(point.x, point.y)); 273 | 274 | // x,y,z given so we have elevation data 275 | if ( typeof elevation !== 'undefined' ) 276 | return r360.latLng([latlng.lat, latlng.lng, elevation]); 277 | // no elevation given, just unproject coordinates to lat/lng 278 | else 279 | return latlng; 280 | }, 281 | 282 | latLngToWebMercator : function(latlng){ 283 | 284 | var point = r360.Projection.SphericalMercator.project(latlng); 285 | point.x *= 6378137; 286 | point.y *= 6378137; 287 | return point; 288 | }, 289 | 290 | getUserAgent : function(){ 291 | var ua= navigator.userAgent, tem, 292 | M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; 293 | if(/trident/i.test(M[1])){ 294 | tem= /\brv[ :]+(\d+)/g.exec(ua) || []; 295 | return 'IE '+(tem[1] || ''); 296 | } 297 | if(M[1]=== 'Chrome'){ 298 | tem= ua.match(/\bOPR\/(\d+)/) 299 | if(tem!= null) return 'Opera '+tem[1]; 300 | } 301 | M= M[2]? [M[1], M[2]]: [navigator.appName, navigator.appVersion, '-?']; 302 | if((tem= ua.match(/version\/(\d+)/i))!= null) M.splice(1, 1, tem[1]); 303 | return M.join(' '); 304 | }, 305 | 306 | /** 307 | * [isAnimated description] 308 | * @return {Boolean} [description] 309 | */ 310 | isAnimated: function(){ 311 | 312 | var userAgent = getUserAgent(); 313 | 314 | if ( userAgent.indexOf("IE") != -1 ) 315 | return false; 316 | if ( userAgent.indexOf("Safari") != -1 ) 317 | return false; 318 | if ( userAgent.indexOf("Firefox") != -1 ) 319 | return false; 320 | if ( r360.config.defaultPolygonLayerOptions.animate ) 321 | return true; 322 | 323 | return false; 324 | }, 325 | 326 | /** 327 | * [getTranslation description] 328 | * @param {type} offset [description] 329 | * @return {type} [description] 330 | */ 331 | getTranslation: function(offset){ 332 | 333 | var userAgent = r360.Util.getUserAgent(); 334 | 335 | if ( userAgent.indexOf("IE 9") != -1 ) 336 | return "transform:translate(" + offset.x + "px," + offset.y + "px)"; 337 | 338 | if ( userAgent.indexOf("Safari") != -1 ) 339 | return "-webkit-transform:translate3d(" + offset.x + "px," + offset.y + "px,0px)"; 340 | 341 | if ( userAgent.indexOf("Firefox") != -1 ) 342 | return "-moz-transform:translate3d(" + offset.x + "px," + offset.y + "px,0px)"; 343 | 344 | else 345 | return "transform:translate3d(" + offset.x + "px," + offset.y + "px,0px)"; 346 | }, 347 | 348 | // round a given number to a given precision 349 | formatNum: function (num, digits) { 350 | var pow = Math.pow(10, digits || 5); 351 | return Math.round(num * pow) / pow; 352 | }, 353 | 354 | isArray : Array.isArray || function (obj) { 355 | return (Object.prototype.toString.call(obj) === '[object Array]'); 356 | }, 357 | 358 | // extend an object with properties of one or more other objects 359 | extend: function (dest) { 360 | var i, j, len, src; 361 | 362 | for (j = 1, len = arguments.length; j < len; j++) { 363 | src = arguments[j]; 364 | for (i in src) { 365 | dest[i] = src[i]; 366 | } 367 | } 368 | return dest; 369 | }, 370 | 371 | // return the length of 1 degree in meters, for both latitude and longitude, based on a given latitude 372 | //http://pordlabs.ucsd.edu/matlab/coord.htm 373 | degreeInMeters: function(lat) { 374 | var rlat = lat * (Math.PI / 180) 375 | 376 | var latlen = 111132.92 - 559.82 * Math.cos(2 * rlat) + 1.175 * Math.cos(4 * rlat); 377 | 378 | var lnglen = 111415.13 * Math.cos(rlat) - 94.55 * Math.cos(3 * rlat); 379 | 380 | return { 381 | lat: latlen, 382 | lng: lnglen 383 | } 384 | 385 | }, 386 | 387 | // return the degrees of a set distance in meters, for both latitude and longitude 388 | metersInDegrees: function(m, lat) { 389 | var degreeLengths = this.degreeInMeters(lat); 390 | 391 | return { 392 | lat: m / degreeLengths.lat, 393 | lng: m / degreeLengths.lng 394 | } 395 | } 396 | }; 397 | 398 | r360.extend = r360.Util.extend; -------------------------------------------------------------------------------- /test/TravelOptionsSpec.js: -------------------------------------------------------------------------------- 1 | describe("the TravelOptions object", function() { 2 | 3 | it("should return the current time including seconds", function() { 4 | 5 | var travelOptions = r360.travelOptions(); 6 | travelOptions.addSource({ lat : 52.21 , lon : 13.37 }); 7 | travelOptions.addSource({ lat : 52.21 , lon : 13.37 }); 8 | travelOptions.addSource({ lat : 52.21 , lon : 13.37 }); 9 | travelOptions.addTarget({ lat : 52.21 , lon : 13.37 }); 10 | travelOptions.addTarget({ lat : 52.21 , lon : 13.37 }); 11 | travelOptions.setTravelTimes([600, 1200, 1800, 2400, 3000, 3600, 4200]); 12 | travelOptions.setTravelType('transit'); 13 | travelOptions.setDate('20140904'); 14 | travelOptions.setTime('43200'); 15 | travelOptions.setIntersectionMode('average'); 16 | travelOptions.setPathSerializer('compact'); 17 | travelOptions.setMaxRoutingTime(3600); 18 | travelOptions.setMaxRoutingLength(100000); 19 | travelOptions.setBikeSpeed(4); 20 | travelOptions.setBikeUphill(5); 21 | travelOptions.setBikeDownhill(6); 22 | travelOptions.setWalkSpeed(1); 23 | travelOptions.setWalkUphill(2); 24 | travelOptions.setWalkDownhill(3); 25 | 26 | expect(travelOptions.getSources()).toBeArrayOfSize(3); 27 | expect(travelOptions.getTargets()).toBeArrayOfSize(2); 28 | expect(travelOptions.getTravelTimes()).toEqual([600, 1200, 1800, 2400, 3000, 3600, 4200]); 29 | expect(travelOptions.getTravelType()).toBe('transit'); 30 | expect(travelOptions.getDate()).toBe('20140904'); 31 | expect(travelOptions.getTime()).toBe('43200'); 32 | expect(travelOptions.getIntersectionMode()).toBe('average'); 33 | expect(travelOptions.getPathSerializer()).toBe('compact'); 34 | expect(travelOptions.getMaxRoutingTime()).toBe(3600); 35 | expect(travelOptions.getMaxRoutingLength()).toBe(100000); 36 | expect(travelOptions.getBikeSpeed()).toBe(4); 37 | expect(travelOptions.getBikeUphill()).toBe(5); 38 | expect(travelOptions.getBikeDownhill()).toBe(6); 39 | expect(travelOptions.getWalkSpeed()).toBe(1); 40 | expect(travelOptions.getWalkUphill()).toBe(2); 41 | expect(travelOptions.getWalkDownhill()).toBe(3); 42 | expect(travelOptions.isValidPolygonServiceOptions()).toBe(true); 43 | expect(travelOptions.isValidRouteServiceOptions()).toBe(true); 44 | expect(travelOptions.isValidTimeServiceOptions()).toBe(true); 45 | expect(travelOptions.getErrors()).toBeArrayOfSize(0); 46 | }); 47 | }); -------------------------------------------------------------------------------- /test/UtilSpec.js: -------------------------------------------------------------------------------- 1 | describe("the Route360° Util", function() { 2 | 3 | it("should return the current time including seconds", function() { 4 | 5 | var now = new Date(); 6 | now = (now.getHours() * 3600) + (now.getMinutes() * 60) + now.getSeconds(); 7 | 8 | expect(r360.Util.getTimeInSeconds()).toBe(now); 9 | }); 10 | 11 | it("should return the current time not including seconds", function() { 12 | 13 | var now = new Date(); 14 | now = (now.getHours() * 3600) + (now.getMinutes() * 60); 15 | 16 | expect(r360.Util.getHoursAndMinutesInSeconds()).toBe(now); 17 | }); 18 | 19 | it("should return the current date in the correct format", function() { 20 | 21 | var date = new Date(); 22 | var year = date.getFullYear(); 23 | var month = (date.getMonth() + 1) < 10 ? "0" + (date.getMonth() + 1) : (date.getMonth() + 1); 24 | var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); 25 | 26 | expect(r360.Util.getCurrentDate()).toBe(year + "" + month + "" + day); 27 | }); 28 | 29 | it("should return the current date in the correct format", function() { 30 | 31 | r360.config.i18n.language = 'en'; 32 | 33 | expect(r360.Util.getTimeFormat(0)).toEqual('a.m.'); 34 | expect(r360.Util.getTimeFormat(3600)).toEqual('a.m.'); 35 | expect(r360.Util.getTimeFormat(43200)).toEqual('p.m.'); 36 | expect(r360.Util.getTimeFormat(86400)).toEqual('a.m.'); 37 | 38 | r360.config.i18n.language = 'de'; 39 | 40 | expect(r360.Util.getTimeFormat(0)).toEqual('Uhr'); 41 | expect(r360.Util.getTimeFormat(3600)).toEqual('Uhr'); 42 | expect(r360.Util.getTimeFormat(43200)).toEqual('Uhr'); 43 | expect(r360.Util.getTimeFormat(86400)).toEqual('Uhr'); 44 | }); 45 | 46 | it("should correctly translate seconds to h/m representation", function(){ 47 | 48 | expect(r360.Util.secondsToHoursAndMinutes(0)).toBe("0min"); 49 | expect(r360.Util.secondsToHoursAndMinutes(29)).toBe("0min"); 50 | expect(r360.Util.secondsToHoursAndMinutes(30)).toBe("1min"); 51 | expect(r360.Util.secondsToHoursAndMinutes(43200)).toBe("12h 0min"); 52 | expect(r360.Util.secondsToHoursAndMinutes(43230)).toBe("12h 1min"); 53 | }); 54 | 55 | it("should transform seconds to the correct time", function(){ 56 | 57 | expect(r360.Util.secondsToTimeOfDay(43200)).toBe("12:00:00"); 58 | expect(r360.Util.secondsToTimeOfDay(40079)).toBe("11:07:59"); 59 | expect(r360.Util.secondsToTimeOfDay(172800)).toBe("48:00:00"); 60 | }); 61 | 62 | it("should generate random strings", function(){ 63 | 64 | expect(r360.Util.generateId().length).toBe(10); 65 | expect(r360.Util.generateId(5).length).toBe(5); 66 | expect(r360.Util.generateId(-1).length).toBe(0); 67 | }); 68 | 69 | it("should create correct latlon arrays", function(){ 70 | 71 | var latlons = [[0,1],[2,3],[4,5],[6,7],[8,9],[10,11]]; 72 | expect(r360.Util.parseLatLonArray(latlons).length).toBe(6); 73 | expect(r360.Util.parseLatLonArray(latlons)[3].x).toBe(7); 74 | expect(r360.Util.parseLatLonArray(latlons)[4].y).toBe(8); 75 | }); 76 | 77 | it("should generate valid polygons", function(){ 78 | 79 | var multiPolygon = r360.Util.parsePolygons(transitPolygon); 80 | expect(multiPolygon.length).toBe(6); // we have travel times for [600, 1200, 1800, 2400, 3000, 3600] 81 | 82 | _.each(multiPolygon, function(polygons){ 83 | // each multipolygon should contain many inner polygons 84 | expect(polygons.polygons.length).toBeGreaterThan(0); 85 | 86 | _.each(polygons.polygons, function(p){ 87 | 88 | // each polygon should have a travel time 89 | expect(p.getTravelTime()).toBeGreaterThan(0); 90 | expect(p.getCenterPoint()).toBeDefined(); 91 | }) 92 | }); 93 | }); 94 | 95 | it("should generate valid routes", function(){ 96 | 97 | var routes = r360.Util.parseRoutes(transitRoute); 98 | expect(routes).toBeDefined(); 99 | expect(routes.length).toBe(1); 100 | 101 | var route = routes[0]; 102 | expect(route.getSegments().length).toBe(5); 103 | expect(route.getTravelTime()).toBe(3112); 104 | expect(route.getDistance()).toBeGreaterThan(0); 105 | 106 | _.each(route.getSegments(), function(segment){ 107 | 108 | // walk is the shortest type 109 | expect(segment.getType().length).toBeGreaterThan(3); 110 | expect(segment.getTravelTime()).toBeGreaterThan(0); 111 | expect(segment.getColor().length).toBeGreaterThan(0); 112 | 113 | if ( segment.getType() != 'TRANSFER' ) 114 | expect(segment.getPoints().length).toBeGreaterThan(0); 115 | 116 | if ( segment.getType() == 'TRANSIT' ) { 117 | 118 | expect(segment.isTransit()).toBe(true); 119 | expect(segment.getDepartureTime()).toBeGreaterThan(0); 120 | expect(segment.getArrivalTime()).toBeGreaterThan(0); 121 | expect(segment.getTripHeadSign().length).toBeGreaterThan(0); 122 | expect(segment.getDistance()).toBeGreaterThan(0); 123 | expect(segment.getStartName().length).toBeGreaterThan(0); 124 | expect(segment.getEndName().length).toBeGreaterThan(0); 125 | } 126 | }); 127 | }); 128 | }); -------------------------------------------------------------------------------- /test/dummydata/transit-route.js: -------------------------------------------------------------------------------- 1 | var transitRoute = { 2 | "routes": [ 3 | { 4 | "travelTime": 3112, 5 | "length": 12823749, 6 | "elevationGain": 0, 7 | "id": "52.449314140869696;13.260841369628906", 8 | "segments": [ 9 | { 10 | "travelTime": 639, 11 | "length": 0.810148465507, 12 | "endname": "target", 13 | "warning": "UNDEFINED", 14 | "elevationGain": 0, 15 | "type": "WALK", 16 | "points": [ 17 | [ 18 | 6881734, 19 | 1476214 20 | ],[ 21 | 6881950, 22 | 1476329 23 | ],[ 24 | 6881950, 25 | 1476329 26 | ],[ 27 | 6881983, 28 | 1476367 29 | ],[ 30 | 6881983, 31 | 1476367 32 | ],[ 33 | 6881997, 34 | 1476363 35 | ],[ 36 | 6881997, 37 | 1476363 38 | ],[ 39 | 6882000, 40 | 1476367 41 | ],[ 42 | 6882000, 43 | 1476367 44 | ],[ 45 | 6882089, 46 | 1476377 47 | ],[ 48 | 6882089, 49 | 1476377 50 | ],[ 51 | 6882112, 52 | 1476379 53 | ],[ 54 | 6882112, 55 | 1476379 56 | ],[ 57 | 6882161, 58 | 1476386 59 | ],[ 60 | 6882161, 61 | 1476386 62 | ],[ 63 | 6882090, 64 | 1477042 65 | ],[ 66 | 6882090, 67 | 1477042 68 | ],[ 69 | 6882079, 70 | 1477143 71 | ],[ 72 | 6882079, 73 | 1477143 74 | ],[ 75 | 6882022, 76 | 1477195 77 | ],[ 78 | 6882022, 79 | 1477195 80 | ],[ 81 | 6882030, 82 | 1477203 83 | ] 84 | ] 85 | }, 86 | { 87 | "departureTime": 55860, 88 | "travelTime": 1080, 89 | "length": 8569121.2835848, 90 | "elevationGain": 0, 91 | "type": "TRANSIT", 92 | "startname": "U Wittenbergplatz (Berlin)", 93 | "points": [ 94 | [ 95 | 6882030, 96 | 1477203 97 | ],[ 98 | 6882086, 99 | 1478506 100 | ],[ 101 | 6882086, 102 | 1478506 103 | ],[ 104 | 6883309, 105 | 1479437 106 | ],[ 107 | 6883309, 108 | 1479437 109 | ],[ 110 | 6884492, 111 | 1480015 112 | ],[ 113 | 6884492, 114 | 1480015 115 | ],[ 116 | 6885072, 117 | 1481581 118 | ],[ 119 | 6885072, 120 | 1481581 121 | ],[ 122 | 6886007, 123 | 1482150 124 | ],[ 125 | 6886007, 126 | 1482150 127 | ],[ 128 | 6887284, 129 | 1481867 130 | ],[ 131 | 6887284, 132 | 1481867 133 | ],[ 134 | 6889250, 135 | 1482165 136 | ],[ 137 | 6889250, 138 | 1482165 139 | ],[ 140 | 6889914, 141 | 1483226 142 | ],[ 143 | 6889914, 144 | 1483226 145 | ],[ 146 | 6890416, 147 | 1483957 148 | ],[ 149 | 6890416, 150 | 1483957 151 | ],[ 152 | 6891143, 153 | 1484642 154 | ],[ 155 | 6891143, 156 | 1484642 157 | ],[ 158 | 6891427, 159 | 1485287 160 | ] 161 | ], 162 | "routeShortName": "U3", 163 | "arrivalTime": 56940, 164 | "endname": "U Oskar-Helene-Heim (Berlin)", 165 | "warning": "UNDEFINED", 166 | "routeType": 400, 167 | "tripHeadSign": "U Krumme Lanke (Berlin)", 168 | "isTransit": true 169 | }, 170 | { 171 | "travelTime": 120, 172 | "length": 0, 173 | "endname": "U Wittenbergplatz (Berlin)", 174 | "warning": "UNDEFINED", 175 | "elevationGain": 0, 176 | "type": "TRANSFER", 177 | "startname": "U Wittenbergplatz (Berlin)", 178 | "points": [ 179 | 180 | ] 181 | }, 182 | { 183 | "departureTime": 55140, 184 | "travelTime": 600, 185 | "length": 4254628.5626928, 186 | "elevationGain": 0, 187 | "type": "TRANSIT", 188 | "startname": "U Stadtmitte U2 (Berlin)", 189 | "points": [ 190 | [ 191 | 6891427, 192 | 1485287 193 | ],[ 194 | 6890976, 195 | 1486540 196 | ],[ 197 | 6890976, 198 | 1486540 199 | ],[ 200 | 6890613, 201 | 1487501 202 | ],[ 203 | 6890613, 204 | 1487501 205 | ],[ 206 | 6890966, 207 | 1488819 208 | ],[ 209 | 6890966, 210 | 1488819 211 | ],[ 212 | 6891737, 213 | 1488866 214 | ],[ 215 | 6891737, 216 | 1488866 217 | ],[ 218 | 6892700, 219 | 1489229 220 | ],[ 221 | 6892700, 222 | 1489229 223 | ],[ 224 | 6893148, 225 | 1489877 226 | ],[ 227 | 6893148, 228 | 1489877 229 | ],[ 230 | 6893267, 231 | 1490535 232 | ] 233 | ], 234 | "routeShortName": "U2", 235 | "arrivalTime": 55740, 236 | "endname": "U Wittenbergplatz (Berlin)", 237 | "warning": "UNDEFINED", 238 | "routeType": 400, 239 | "tripHeadSign": "U Theodor-Heuss-Platz (Berlin)", 240 | "isTransit": true 241 | }, 242 | { 243 | "travelTime": 673, 244 | "length": 0.75488114913111, 245 | "endname": "target", 246 | "warning": "UNDEFINED", 247 | "elevationGain": 0, 248 | "type": "WALK", 249 | "startname": "source", 250 | "points": [ 251 | [ 252 | 6893267, 253 | 1490535 254 | ],[ 255 | 6893262, 256 | 1490535 257 | ],[ 258 | 6893262, 259 | 1490535 260 | ],[ 261 | 6893263, 262 | 1490524 263 | ],[ 264 | 6893263, 265 | 1490524 266 | ],[ 267 | 6893413, 268 | 1490510 269 | ],[ 270 | 6893413, 271 | 1490510 272 | ],[ 273 | 6893575, 274 | 1490494 275 | ],[ 276 | 6893575, 277 | 1490494 278 | ],[ 279 | 6893620, 280 | 1490481 281 | ],[ 282 | 6893620, 283 | 1490481 284 | ],[ 285 | 6893648, 286 | 1490479 287 | ],[ 288 | 6893648, 289 | 1490479 290 | ],[ 291 | 6893696, 292 | 1490482 293 | ],[ 294 | 6893696, 295 | 1490482 296 | ],[ 297 | 6893737, 298 | 1490478 299 | ],[ 300 | 6893737, 301 | 1490478 302 | ],[ 303 | 6893772, 304 | 1490473 305 | ],[ 306 | 6893772, 307 | 1490473 308 | ],[ 309 | 6893836, 310 | 1490474 311 | ],[ 312 | 6893836, 313 | 1490474 314 | ],[ 315 | 6893859, 316 | 1490472 317 | ],[ 318 | 6893859, 319 | 1490472 320 | ],[ 321 | 6893899, 322 | 1490465 323 | ],[ 324 | 6893899, 325 | 1490465 326 | ],[ 327 | 6893999, 328 | 1490456 329 | ],[ 330 | 6893999, 331 | 1490456 332 | ],[ 333 | 6894150, 334 | 1490440 335 | ],[ 336 | 6894150, 337 | 1490440 338 | ],[ 339 | 6894181, 340 | 1490437 341 | ],[ 342 | 6894181, 343 | 1490437 344 | ],[ 345 | 6894336, 346 | 1490420 347 | ],[ 348 | 6894336, 349 | 1490420 350 | ],[ 351 | 6894353, 352 | 1490564 353 | ] 354 | ] 355 | } 356 | ] 357 | } 358 | ] 359 | } -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Sep 04 2014 17:37:31 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine', 'jasmine-matchers'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'debug/demo/lib/jquery/jquery-1.10.2.js', 19 | 'debug/demo/lib/leaflet/leaflet.js', 20 | 'debug/demo/js/underscore.js', 21 | 'debug/demo/lib/proj4/proj4-compressed.js', 22 | 'debug/demo/lib/proj4/proj4leaflet.js', 23 | 'src/r360.js', 24 | 'src/r360-defaults.js', 25 | 'src/util/Util.js', 26 | 'src/util/TravelOptions.js', 27 | 'src/api/polygons/PolygonService.js', 28 | 'src/api/routes/RouteService.js', 29 | 'src/api/time/TimeService.js', 30 | 'src/control/PlaceAutoCompleteControl.js', 31 | 'src/control/TravelStartDateControl.js', 32 | 'src/control/TravelStartTimeControl.js', 33 | 'src/control/TravelTimeControl.js', 34 | 'src/control/WaitControl.js', 35 | 'src/control/HtmlControl.js', 36 | 'src/control/RadioButtonControl.js', 37 | 'src/control/CheckboxButtonControl.js', 38 | 'src/geometry/polygon/Polygon.js', 39 | 'src/geometry/polygon/MultiPolygon.js', 40 | 'src/geometry/route/RouteSegment.js', 41 | 'src/geometry/route/Route.js', 42 | 'src/layer/Route360PolygonLayer.js', 43 | 'spec/**/*.js' 44 | ], 45 | 46 | 47 | // list of files to exclude 48 | exclude: [ 49 | ], 50 | 51 | 52 | // preprocess matching files before serving them to the browser 53 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 54 | preprocessors: { 55 | }, 56 | 57 | 58 | // test results reporter to use 59 | // possible values: 'dots', 'progress' 60 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 61 | reporters: ['progress'], 62 | 63 | 64 | // web server port 65 | port: 9876, 66 | 67 | 68 | // enable / disable colors in the output (reporters and logs) 69 | colors: true, 70 | 71 | 72 | // level of logging 73 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 74 | logLevel: config.LOG_INFO, 75 | 76 | 77 | // enable / disable watching file and executing tests whenever any file changes 78 | autoWatch: true, 79 | 80 | 81 | // start these browsers 82 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 83 | browsers: [], 84 | 85 | 86 | // Continuous Integration mode 87 | // if true, Karma captures browsers, runs the tests and exits 88 | singleRun: false, 89 | 90 | 91 | 92 | reportSlowerThan : 500 93 | }); 94 | }; 95 | --------------------------------------------------------------------------------