├── .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 | 
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 = "";
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 = "";
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 |
--------------------------------------------------------------------------------