UNPKG

route360

Version:

The Route360° JavaScript API for Motion Intelligence's time-based access mapping service.

1,551 lines (1,238 loc) 164 kB
/* Route360° JavaScript API v0.5.3 (a575c9e), a JS library for leaflet.js and google maps API. http://route360.net (c) 2017 Henning Hollburg, Daniel Gerber and Jan Silbersiepe, (c) 2017 Motion Intelligence GmbH */ (function (window, document, undefined) { var r360 = { version : 'v0.5.3', // Is a given variable undefined? isUndefined : function(obj) { return obj === void 0; }, // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). has : function(obj, key) { return obj != null && hasOwnProperty.call(obj, key); }, // is a given object a function isFunction : function(obj) { return typeof obj == 'function' || false; }, findWhere : function(array, attr) { var result = undefined; array.some(function(elem,index,array){ var match = false; for(var index in attr) { match = (r360.has(elem,index) && elem[index] === attr[index]) ? true : false; } if (match) { result = elem; return true; } }); return result; }, filter : function(array,predicate) { var results = []; array.forEach(function(elem,index,array){ if (predicate(elem, index, array)) results.push(elem); }); return results; }, contains : function(array,item) { return array.indexOf(item) > -1; }, each : function(array,cb) { array.forEach(function(elem,index,array){ cb(elem,index,array); }); }, max : function(array, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null || (typeof iteratee == 'number' && typeof array[0] != 'arrayect') && array != null) { for (var i = 0, length = array.length; i < length; i++) { value = array[i]; if (value != null && value > result) { result = value; } } } else { r360.each(array, function(elem, index, array) { computed = iteratee(elem, index, array); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = elem; lastComputed = computed; } }); } return result; }, keys : function(obj) { if (typeof obj !== 'Object') return []; if (Object.keys(obj)) return Object.keys(obj); var keys = []; for (var key in obj) if (r360.has(obj, key)) keys.push(key); return keys; } }; function expose() { var oldr360 = window.r360; r360.noConflict = function () { window.r360 = oldr360; return this; }; window.r360 = r360; } // define r360 for Node module pattern loaders, including Browserify if (typeof module === 'object' && typeof module.exports === 'object') module.exports = r360; // define r360 as an AMD module else if (typeof define === 'function' && define.amd) define(r360); // define r360 as a global r360 variable, saving the original r360 to restore later if needed else expose(); /* * IE 8 does not get the bind function. This is a workaround... */ if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; } r360.config = { serviceVersion : 'v1', pathSerializer : 'compact', requestTimeout : 10000, maxRoutingTime : 3600, maxRoutingLength : 100000, bikeSpeed : 15, bikeUphill : 20, bikeDownhill : -10, walkSpeed : 5, walkUphill : 10, walkDownhill : 0, travelTimes : [300, 600, 900, 1200, 1500, 1800], travelType : "walk", logging : false, rushHour : false, // options for the travel time slider; colors and lengths etc. defaultTravelTimeControlOptions : { travelTimes : [ { time : 300 , color : "#006837", opacity : 0.1 }, { time : 600 , color : "#39B54A", opacity : 0.2 }, { time : 900 , color : "#8CC63F", opacity : 0.3 }, { time : 1200 , color : "#F7931E", opacity : 0.4 }, { time : 1500 , color : "#F15A24", opacity : 0.5 }, { time : 1800 , color : "#C1272D", opacity : 1.0 } ], position : 'topright', label: 'travel time', initValue: 30 }, routeTypes : [ // non transit { routeType : 'WALK' , color : "red", haloColor : "white"}, { routeType : 'BIKE' , color : "#558D54", haloColor : "white"}, { routeType : 'CAR' , color : "#558D54", haloColor : "white"}, { routeType : 'TRANSFER' , color : "#C1272D", haloColor : "white"}, // berlin { routeType : 102 , color : "#006837", haloColor : "white" }, { routeType : 400 , color : "#156ab8", haloColor : "white" }, { routeType : 900 , color : "red", haloColor : "white" }, { routeType : 700 , color : "#A3007C", haloColor : "white" }, { routeType : 1000 , color : "blue", haloColor : "white" }, { routeType : 109 , color : "#006F35", haloColor : "white" }, { routeType : 100 , color : "red", haloColor : "white" }, // new york { routeType : 1 , color : "red", haloColor : "red"}, { routeType : 2 , color : "blue", haloColor : "blue"}, { routeType : 3 , color : "yellow", haloColor : "yellow"}, { routeType : 0 , color : "green", haloColor : "green"}, { routeType : 4 , color : "orange", haloColor : "orange"}, { routeType : 5 , color : "red", haloColor : "red"}, { routeType : 6 , color : "blue", haloColor : "blue"}, { routeType : 7 , color : "yellow", haloColor : "yellow" } ], photonPlaceAutoCompleteOptions : { serviceUrl : "https://service.route360.net/geocode/", position : 'topleft', reset : false, reverse : false, placeholder : 'Select source', maxRows : 5, width : 300 }, defaultRadioOptions: { position : 'topright', }, // configuration for the Route360PolygonLayer defaultPolygonLayerOptions:{ opacity : 0.4, strokeWidth: 30, tolerance: 15, // background values only matter if inverse = true backgroundColor : 'black', backgroundOpacity : 0.5, inverse : false, animate : false, animationDuration : 1 } } /* * r360.Bounds represents a rectangular area on the screen in pixel coordinates. */ r360.Bounds = function (a, b) { //(Point, Point) or Point[] if (!a) { return; } var points = b ? [a, b] : a; for (var i = 0, len = points.length; i < len; i++) { this.extend(points[i]); } }; r360.Bounds.prototype = { // extend the bounds to contain the given point extend: function (point) { // (Point) point = r360.point(point); if (!this.min && !this.max) { this.min = point.clone(); this.max = point.clone(); } else { this.min.x = Math.min(point.x, this.min.x); this.max.x = Math.max(point.x, this.max.x); this.min.y = Math.min(point.y, this.min.y); this.max.y = Math.max(point.y, this.max.y); } return this; }, getCenter: function (round) { // (Boolean) -> Point return new r360.Point( (this.min.x + this.max.x) / 2, (this.min.y + this.max.y) / 2, round); }, getBottomLeft: function () { // -> Point return new r360.Point(this.min.x, this.max.y); }, getTopRight: function () { // -> Point return new r360.Point(this.max.x, this.min.y); }, getSize: function () { return this.max.subtract(this.min); }, contains: function (obj) { // (Bounds) or (Point) -> Boolean var min, max; if (typeof obj[0] === 'number' || obj instanceof r360.Point) { obj = r360.point(obj); } else { obj = r360.bounds(obj); } if (obj instanceof r360.Bounds) { min = obj.min; max = obj.max; } else { min = max = obj; } return (min.x >= this.min.x) && (max.x <= this.max.x) && (min.y >= this.min.y) && (max.y <= this.max.y); }, intersects: function (bounds) { // (Bounds) -> Boolean bounds = r360.bounds(bounds); var min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xIntersects = (max2.x >= min.x) && (min2.x <= max.x), yIntersects = (max2.y >= min.y) && (min2.y <= max.y); return xIntersects && yIntersects; }, overlaps: function (bounds) { // (Bounds) -> Boolean bounds = r360.bounds(bounds); var min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xOverlaps = (max2.x > min.x) && (min2.x < max.x), yOverlaps = (max2.y > min.y) && (min2.y < max.y); return xOverlaps && yOverlaps; }, isValid: function () { return !!(this.min && this.max); } }; r360.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) if (!a || a instanceof r360.Bounds) { return a; } return new r360.Bounds(a, b); }; /* * r360.LatLngBounds represents a rectangular area on the map in geographical coordinates. */ r360.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) if (!southWest) { return; } var latlngs = northEast ? [southWest, northEast] : southWest; for (var i = 0, len = latlngs.length; i < len; i++) { this.extend(latlngs[i]); } }; r360.LatLngBounds.prototype = { // extend the bounds to contain the given point or bounds extend: function (obj) { // (LatLng) or (LatLngBounds) var sw = this._southWest, ne = this._northEast, sw2, ne2; if (obj instanceof r360.LatLng) { sw2 = obj; ne2 = obj; } else if (obj instanceof r360.LatLngBounds) { sw2 = obj._southWest; ne2 = obj._northEast; if (!sw2 || !ne2) { return this; } } else { return obj ? this.extend(r360.latLng(obj) || r360.latLngBounds(obj)) : this; } if (!sw && !ne) { this._southWest = new r360.LatLng(sw2.lat, sw2.lng); this._northEast = new r360.LatLng(ne2.lat, ne2.lng); } else { sw.lat = Math.min(sw2.lat, sw.lat); sw.lng = Math.min(sw2.lng, sw.lng); ne.lat = Math.max(ne2.lat, ne.lat); ne.lng = Math.max(ne2.lng, ne.lng); } return this; }, // extend the bounds by a percentage pad: function (bufferRatio) { // (Number) -> LatLngBounds var sw = this._southWest, ne = this._northEast, heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; return new r360.LatLngBounds( new r360.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), new r360.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); }, getCenter: function () { // -> LatLng return new r360.LatLng( (this._southWest.lat + this._northEast.lat) / 2, (this._southWest.lng + this._northEast.lng) / 2); }, getSouthWest: function () { return this._southWest; }, getNorthEast: function () { return this._northEast; }, getNorthWest: function () { return new r360.LatLng(this.getNorth(), this.getWest()); }, getSouthEast: function () { return new r360.LatLng(this.getSouth(), this.getEast()); }, getWest: function () { return this._southWest.lng; }, getSouth: function () { return this._southWest.lat; }, getEast: function () { return this._northEast.lng; }, getNorth: function () { return this._northEast.lat; }, contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean if (typeof obj[0] === 'number' || obj instanceof r360.LatLng) { obj = r360.latLng(obj); } else { obj = r360.latLngBounds(obj); } var sw = this._southWest, ne = this._northEast, sw2, ne2; if (obj instanceof r360.LatLngBounds) { sw2 = obj.getSouthWest(); ne2 = obj.getNorthEast(); } else { sw2 = ne2 = obj; } return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); }, intersects: function (bounds) { // (LatLngBounds) -> Boolean bounds = r360.latLngBounds(bounds); var sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); return latIntersects && lngIntersects; }, overlaps: function (bounds) { // (LatLngBounds) -> Boolean bounds = r360.latLngBounds(bounds); var sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); return latOverlaps && lngOverlaps; }, toBBoxString: function () { return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); }, equals: function (bounds) { // (LatLngBounds) if (!bounds) { return false; } bounds = r360.latLngBounds(bounds); return this._southWest.equals(bounds.getSouthWest()) && this._northEast.equals(bounds.getNorthEast()); }, isValid: function () { return !!(this._southWest && this._northEast); } }; //TODO International date line? r360.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) if (!a || a instanceof r360.LatLngBounds) { return a; } return new r360.LatLngBounds(a, b); }; /* * r360.Point represents a point with x and y coordinates. */ r360.Point = function (x, y, round) { this.x = (round ? Math.round(x) : x); this.y = (round ? Math.round(y) : y); }; r360.Point.prototype = { clone: function () { return new r360.Point(this.x, this.y); }, // non-destructive, returns a new point add: function (point) { return this.clone()._add(r360.point(point)); }, // destructive, used directly for performance in situations where it's safe to modify existing point _add: function (point) { this.x += point.x; this.y += point.y; return this; }, subtract: function (point) { return this.clone()._subtract(r360.point(point)); }, _subtract: function (point) { this.x -= point.x; this.y -= point.y; return this; }, divideBy: function (num) { return this.clone()._divideBy(num); }, _divideBy: function (num) { this.x /= num; this.y /= num; return this; }, multiplyBy: function (num) { return this.clone()._multiplyBy(num); }, _multiplyBy: function (num) { this.x *= num; this.y *= num; return this; }, round: function () { return this.clone()._round(); }, _round: function () { this.x = Math.round(this.x); this.y = Math.round(this.y); return this; }, floor: function () { return this.clone()._floor(); }, _floor: function () { this.x = Math.floor(this.x); this.y = Math.floor(this.y); return this; }, ceil: function () { return this.clone()._ceil(); }, _ceil: function () { this.x = Math.ceil(this.x); this.y = Math.ceil(this.y); return this; }, distanceTo: function (point) { point = r360.point(point); var x = point.x - this.x, y = point.y - this.y; return Math.sqrt(x * x + y * y); }, equals: function (point) { point = r360.point(point); return point.x === this.x && point.y === this.y; }, contains: function (point) { point = r360.point(point); return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <= Math.abs(this.y); }, toString: function () { return 'Point(' + r360.Util.formatNum(this.x) + ', ' + r360.Util.formatNum(this.y) + ')'; } }; r360.point = function (x, y, round) { if (x instanceof r360.Point) { return x; } if (r360.Util.isArray(x)) { return new r360.Point(x[0], x[1]); } if (x === undefined || x === null) { return x; } return new r360.Point(x, y, round); }; /* * r360.LatLng represents a geographical point with latitude and longitude coordinates. */ r360.LatLng = function (lat, lng, alt) { if (isNaN(lat) || isNaN(lng)) { throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); } this.lat = +lat; this.lng = +lng; if (alt !== undefined) { this.alt = +alt; } }; r360.LatLng.prototype = { equals: function (obj, maxMargin) { if (!obj) { return false; } obj = r360.latLng(obj); var margin = Math.max( Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng)); return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); }, toString: function (precision) { return 'LatLng(' + r360.Util.formatNum(this.lat, precision) + ', ' + r360.Util.formatNum(this.lng, precision) + ')'; }, distanceTo: function (other) { return r360.CRS.Earth.distance(this, r360.latLng(other)); }, wrap: function () { return r360.CRS.Earth.wrapLatLng(this); }, toBounds: function (sizeInMeters) { var latAccuracy = 180 * sizeInMeters / 40075017, lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); return r360.latLngBounds( [this.lat - latAccuracy, this.lng - lngAccuracy], [this.lat + latAccuracy, this.lng + lngAccuracy]); }, clone: function () { return new r360.LatLng(this.lat, this.lng, this.alt); } }; // constructs LatLng with different signatures // (LatLng) or ([Number, Number]) or (Number, Number) or (Object) r360.latLng = function (a, b, c) { if (a instanceof r360.LatLng) { return a; } if (r360.Util.isArray(a) && typeof a[0] !== 'object') { if (a.length === 3) { return new r360.LatLng(a[0], a[1], a[2]); } if (a.length === 2) { return new r360.LatLng(a[0], a[1]); } return null; } if (a === undefined || a === null) { return a; } if (typeof a === 'object' && 'lat' in a) { return new r360.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); } if (b === undefined) { return null; } return new r360.LatLng(a, b, c); }; /* * r360.Browser handles different browser and feature detections for internal Leaflet use. */ (function () { var ua = navigator.userAgent.toLowerCase(), doc = document.documentElement, ie = 'ActiveXObject' in window, webkit = ua.indexOf('webkit') !== -1, phantomjs = ua.indexOf('phantom') !== -1, android23 = ua.search('android [23]') !== -1, chrome = ua.indexOf('chrome') !== -1, gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie, mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1, msPointer = navigator.msPointerEnabled && navigator.msMaxTouchPoints && !window.PointerEvent, pointer = (window.PointerEvent && navigator.pointerEnabled && navigator.maxTouchPoints) || msPointer, ie3d = ie && ('transition' in doc.style), webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23, gecko3d = 'MozPerspective' in doc.style, opera12 = 'OTransition' in doc.style; var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window || (window.DocumentTouch && document instanceof window.DocumentTouch)); r360.Browser = { ie: ie, ielt9: ie && !document.addEventListener, webkit: webkit, gecko: gecko, android: ua.indexOf('android') !== -1, android23: android23, chrome: chrome, safari: !chrome && ua.indexOf('safari') !== -1, ie3d: ie3d, webkit3d: webkit3d, gecko3d: gecko3d, opera12: opera12, any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs, mobile: mobile, mobileWebkit: mobile && webkit, mobileWebkit3d: mobile && webkit3d, mobileOpera: mobile && window.opera, mobileGecko: mobile && gecko, touch: !!touch, msPointer: !!msPointer, pointer: !!pointer, retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1 }; }()); /* * r360.Class powers the OOP facilities of the library. * Thanks to John Resig and Dean Edwards for inspiration! */ r360.Class = function () {}; r360.Class.extend = function (props) { // extended class with the new prototype var NewClass = function () { // call the constructor if (this.initialize) { this.initialize.apply(this, arguments); } // call all constructor hooks this.callInitHooks(); }; var parentProto = NewClass.__super__ = this.prototype; var proto = r360.Util.create(parentProto); proto.constructor = NewClass; NewClass.prototype = proto; // inherit parent's statics for (var i in this) { if (this.hasOwnProperty(i) && i !== 'prototype') { NewClass[i] = this[i]; } } // mix static properties into the class if (props.statics) { r360.extend(NewClass, props.statics); delete props.statics; } // mix includes into the prototype if (props.includes) { r360.Util.extend.apply(null, [proto].concat(props.includes)); delete props.includes; } // merge options if (proto.options) { props.options = r360.Util.extend(r360.Util.create(proto.options), props.options); } // mix given properties into the prototype r360.extend(proto, props); proto._initHooks = []; // add method for calling all hooks proto.callInitHooks = function () { if (this._initHooksCalled) { return; } if (parentProto.callInitHooks) { parentProto.callInitHooks.call(this); } this._initHooksCalled = true; for (var i = 0, len = proto._initHooks.length; i < len; i++) { proto._initHooks[i].call(this); } }; return NewClass; }; // method for adding properties to prototype r360.Class.include = function (props) { r360.extend(this.prototype, props); }; // merge new default options to the Class r360.Class.mergeOptions = function (options) { r360.extend(this.prototype.options, options); }; // add a constructor hook r360.Class.addInitHook = function (fn) { // (Function) || (String, args...) var args = Array.prototype.slice.call(arguments, 1); var init = typeof fn === 'function' ? fn : function () { this[fn].apply(this, args); }; this.prototype._initHooks = this.prototype._initHooks || []; this.prototype._initHooks.push(init); }; r360.PolygonUtil = { /** * [clip clipping like sutherland http://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#JavaScript] * @param {type} subjectPolygon [description] * @param {type} clipPolygon [description] * @return {type} [description] */ clip: function(subjectPolygon, clipPolygon) { var cp1, cp2, s, e; var inside = function (p) { return (cp2[0]-cp1[0])*(p[1]-cp1[1]) > (cp2[1]-cp1[1])*(p[0]-cp1[0]); }; var intersection = function () { var dc = [ cp1[0] - cp2[0], cp1[1] - cp2[1] ], dp = [ s[0] - e[0], s[1] - e[1] ], n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0], n2 = s[0] * e[1] - s[1] * e[0], n3 = 1.0 / (dc[0] * dp[1] - dc[1] * dp[0]); return [(n1*dp[0] - n2*dc[0]) * n3, (n1*dp[1] - n2*dc[1]) * n3]; }; var outputList = subjectPolygon; var cp1 = clipPolygon[clipPolygon.length-1]; for (j in clipPolygon) { var cp2 = clipPolygon[j]; var inputList = outputList; outputList = []; s = inputList[inputList.length - 1]; //last on the input list for (i in inputList) { var e = inputList[i]; if (inside(e)) { if (!inside(s)) { outputList.push(intersection()); } outputList.push(e); } else if (inside(s)) { outputList.push(intersection()); } s = e; } cp1 = cp2; } return outputList }, /** * [isCollinear Checks if the given three points are collinear. Also see * https://en.wikipedia.org/wiki/Collinearity. This method uses a tolerance * factor defined in r360.config.defaultPolygonLayerOptions.tolerance.] * * @param {type} p1 [description] * @param {type} p2 [description] * @param {type} p3 [description] * @return {Boolean} [description] */ isCollinear: function(p1, p2, p3){ if(p1.x == p3.x && p1.y == p3.y) return false; if(p1.x == p2.x && p2.x == p3.x) return true; if(p1.y == p2.y && p2.y == p3.y) return true; var val = (p1.x * (p2.y -p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)); if ( val < r360.config.defaultPolygonLayerOptions.tolerance && val > -r360.config.defaultPolygonLayerOptions.tolerance && p1.x != p3.x && p1.y != p3.y ) return true; return false; }, /** * [scale Scales a point (x and y coordinate) by the given scale. This method changes * the values of the given point.] * @param {type} point [the point to be scaled] * @param {type} scale [the scale] * @return {type} [the scaled point] */ scale: function(point, scale){ return r360.point(point.x * scale, point.y * scale); }, /** * [subtract Subtracts the given x and y coordinate from the cooresponding values of the given point. * This method changes the values of the given point. ] * @param {type} point [the point to be changed] * @param {type} x [the x value to be subtracted] * @param {type} y [the y value to be subtracted] * @return {type} [the subtracted point] */ subtract: function(point, x, y){ return r360.point(point.x - x, point.y - y); }, divide: function(point, quotient){ return r360.point(point.x / quotient, point.y / quotient); }, /** * [roundPoint Rounds a point's x and y coordinate. The method changes the x and y * values of the given point. If the fractional portion of number (x and y) * is 0.5 or greater, the argument is rounded to the next higher integer. If the * fractional portion of number is less than 0.5, the argument is rounded to the * next lower integer.] * * @param {type} point [the point to rounded] * @return {type} [the point to be rounded with integer x and y coordinate] */ roundPoint: function(point){ point.x = Math.round(point.x); point.y = Math.round(point.y); return point; }, /** * [buildPath Creates an SVG path. ] * @param {type} point [The point to add] * @param {type} suffix [The svg suffix for the point] * @return {type} [An array containing the suffix, point.x, point.y] */ buildPath:function(point, suffix){ return [suffix, Math.round(point.x), Math.round(point.y)]; }, /** * [getEuclidianDistance This method returns the euclidean distance between two points (x and y coordinates).] * @param {type} point1 [the first point] * @param {type} point2 [the second point] * @return {type} [the distance] */ getEuclidianDistance: function(point1, point2){ return Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2)); }, /** * [getSvgFrame description] * @param {type} width [description] * @param {type} height [description] * @return {type} [description] */ getSvgFrame: function(width, height){ return [['M',0, 0], ['L',width, 0], ['L',width, height], ['L',0, height], ['z']]; }, /** * [extendBounds description] * @param {type} bounds [description] * @param {type} extendWidthX [description] * @param {type} extendWidthY [description] * @return {type} [description] */ extendBounds : function(bounds, extendWidthX, extendWidthY) { var extendX = Math.ceil(extendWidthX); var extendY = Math.ceil(extendWidthY); bounds.max.x += extendX; bounds.min.x -= extendX; bounds.max.y += extendY; bounds.min.y -= extendY; return bounds; }, /* * */ addPolygonToMultiPolygon: function(multiPolygons, polygon){ var filteredMultiPolygons = r360.filter(multiPolygons, function(multiPolygon){ return multiPolygon.getTravelTime() == polygon.travelTime; }); // multipolygon with polygon's travetime already there if ( filteredMultiPolygons.length > 0 ) filteredMultiPolygons[0].addPolygon(polygon); else { var multiPolygon = new r360.multiPolygon(); multiPolygon.setTravelTime(polygon.travelTime); multiPolygon.addPolygon(polygon); multiPolygon.setColor(polygon.getColor()); multiPolygon.setOpacity(polygon.getOpacity()); multiPolygons.push(multiPolygon); } }, } r360.SvgUtil = { /** * [getGElement description] * @param {type} svgData [description] * @param {type} opacity [description] * @param {type} color [description] * @param {type} animate [description] * @return {type} [description] */ getGElement : function(svgData, options){ var randomId = r360.Util.generateId(); var initialOpacity = options.opacity; return "<g id=" + randomId + " style='opacity:" + initialOpacity + "'>"+ "<path style='stroke: " + options.color + "; fill: " + options.color + " ; stroke-opacity: 1; stroke-width: " + options.strokeWidth + "; fill-opacity:1'd='" + svgData.toString().replace(/\,/g, ' ') + "'/>"+ "</g>"; }, /** * [getInverseSvgElement description] * @param {type} gElements [description] * @return {type} [description] */ getInverseSvgElement: function(gElements, options){ var svgFrame = r360.PolygonUtil.getSvgFrame(options.svgWidth, options.svgHeight); var svgStart = "<div id=svg_"+ options.id + " style='" + r360.Util.getTranslation(options.offset) + ";''><svg" + " height=" + options.svgHeight + " width=" + options.svgWidth + " style='fill:" + options.backgroundColor + " ; opacity: "+ options.backgroundOpacity + "; stroke-width: " + options.strokeWidth + "; stroke-linejoin:round; stroke-linecap:round; fill-rule: evenodd' xmlns='http://www.w3.org/2000/svg'>" var svgEnd = "</svg></div>"; var newSvg = "<defs>"+ "<mask id='mask_" + options.id + "'>"+ "<path style='fill-opacity:1;stroke: white; fill:white;' d='" + svgFrame.toString().replace(/\,/g, ' ') + "'/>"+ gElements.join('') + "</mask>"+ "</defs>"; var frame = "<path style='mask: url(#mask_" + options.id + ")' d='" + svgFrame.toString().replace(/\,/g, ' ') + "'/>"; return svgStart + frame + newSvg + svgEnd; }, /** * [getNormalSvgElement description] * @param {type} gElement [description] * @return {type} [description] */ getNormalSvgElement: function(gElements, options){ var svgStart = "<div id=svg_"+ options.id + " style='" + r360.Util.getTranslation(options.offset) + ";''><svg " + " height=" + options.svgHeight + " width=" + options.svgWidth + " style='fill:" + options.backgroundColor + " ; opacity: " + options.opacity + "; stroke-linejoin:round; stroke-linecap:round; fill-rule: evenodd' xmlns='http://www.w3.org/2000/svg'>" var svgEnd = "</svg></div>"; return svgStart + gElements.join('') + svgEnd; }, /** * [createSvgData description] * @param {type} polygon [description] * @param {type} options [description] * @return {type} [description] */ createSvgData : function(polygon, options) { var pathData = []; var topRight = r360.PolygonUtil.scale(polygon.getTopRightDecimal(), options.scale); var bottomLeft = r360.PolygonUtil.scale(polygon.getBottomLeftDecimal(), options.scale); // the outer boundary if ( !(bottomLeft.x > options.bounds.max.x || topRight.x < options.bounds.min.x || topRight.y > options.bounds.max.y || bottomLeft.y < options.bounds.min.y )) r360.SvgUtil.buildSVGPolygon(pathData, polygon.getOuterBoundary().getCoordinates(), options); var innerBoundary = polygon.getInnerBoundary(); // the inner boundaries for ( var i = 0 ; i < innerBoundary.length ; i++ ) { var topRightInner = r360.PolygonUtil.scale(innerBoundary[i].getTopRightDecimal(), options.scale); var bottomLeftInner = r360.PolygonUtil.scale(innerBoundary[i].getBottomLeftDecimal(), options.scale); if ( !(bottomLeftInner.x > options.bounds.max.x || topRightInner.x < options.bounds.min.x || topRightInner.y > options.bounds.max.y || bottomLeftInner.y < options.bounds.min.y )) r360.SvgUtil.buildSVGPolygon(pathData, innerBoundary[i].getCoordinates(), options); } return pathData; }, /** * [buildSVGPolygon description] * @param {type} pathData [description] * @param {type} coordinateArray [description] * @param {type} bounds [description] * @param {type} scale [description] * @return {type} [description] */ buildSVGPolygon: function(pathData, coordinateArray, options){ var point, point1, point2, isCollinear, euclidianDistance, pointCount = 0; var boundArray = [[options.bounds.min.x, options.bounds.min.y], [options.bounds.max.x, options.bounds.min.y], [options.bounds.max.x, options.bounds.max.y], [options.bounds.min.x, options.bounds.max.y]]; var pointsToClip = []; for ( var i = 0 ; i < coordinateArray.length ; i++ ) { point = r360.PolygonUtil.scale(r360.point(coordinateArray[i].x, coordinateArray[i].y), options.scale); euclidianDistance = (i > 0) ? r360.PolygonUtil.getEuclidianDistance(point2, point) : options.tolerance; if ( euclidianDistance >= options.tolerance ) { isCollinear = false; if ( pointCount > 2 ) isCollinear = r360.PolygonUtil.isCollinear(point1, point2, point); if ( isCollinear ) { pointsToClip[pointsToClip.length-1][0] = point.x; pointsToClip[pointsToClip.length-1][1] = point.y; } else { pointsToClip.push([point.x, point.y]); point1 = point2; point2 = point; pointCount++; } } } var clippedArray = r360.PolygonUtil.clip(pointsToClip, boundArray); var lastPoint; for ( var i = 0 ; i < clippedArray.length ; i++ ){ point = r360.PolygonUtil.subtract(r360.point(clippedArray[i][0], clippedArray[i][1]), options.pixelOrigin.x + options.offset.x, options.pixelOrigin.y + options.offset.y) pathData.push( i > 0 ? r360.PolygonUtil.buildPath(point, "L") : r360.PolygonUtil.buildPath(point, "M")); lastPoint = point; } if ( pathData.length > 0 ) pathData.push(["z"]); // svgz return pathData; }, } /* * */ r360.Util = { /* * This method returns the current time, at the time this method is executed, * in seconds. This means that the current hours, minutes and seconds of the current * time are added up, e.g.: 12:11:15 pm: * * -> (12 * 3600) + (11 * 60) + 15 = 43875 * * @method getTimeInSeconds * * @returns {Number} The current time in seconds */ getTimeInSeconds : function() { var now = new Date(); return (now.getHours() * 3600) + (now.getMinutes() * 60) + now.getSeconds(); }, /* * This method returns the current time in seconds, rounded down to the nearest minute, * at the time this method is executed. This means that the current hours and minutes of the * current time are converted to seconds and added up, e.g.: 12:11 pm: * * -> (12 * 3600) + (11 * 60) = 43860 * * @method getHoursAndMinutesInSeconds * * @returns {Number} The current time in seconds */ getHoursAndMinutesInSeconds : function() { var now = new Date(); return (now.getHours() * 3600) + (now.getMinutes() * 60); }, /* * Returns the current date in the form 20140508 (YYYYMMDD). Note that month is * not zero but 1 based, which means 6 == June. * * @method getCurrentDate * * @return {String} the date object in string representation YYYYMMDD */ getCurrentDate : function() { var date = new Date(); var year = date.getFullYear(); var month = (date.getMonth() + 1) < 10 ? "0" + (date.getMonth() + 1) : (date.getMonth() + 1); var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); return year + "" + month + "" + day; }, getTimeFormat : function(seconds) { var i18n = r360.config.i18n; if ( i18n.language == 'en' ) if ( seconds >= 43200 ) return 'p.m.'; return i18n.get('timeFormat'); }, /* * Transforms the given seconds to a hour and minuten view. This means * that for example 10:15 (one hour and 15 minutes) is translates to the string: * -> 10h 15min * * Note that no trailing zeros are returned. Also if hours < 1 only minute values will be returned. * * @method secondsToHoursAndMinutes * @returns {String} the transformed seconds in "xh ymin" */ secondsToHoursAndMinutes : function(seconds) { var minutes = (seconds / 60).toFixed(0); var hours = Math.floor(minutes / 60); minutes = minutes - hours * 60; var timeString = ""; if (hours != 0) timeString += (hours + "h "); timeString += (minutes + "min"); return timeString; }, /* * This methods transforms a given time in seconds to a format like: * 43200 -> 12:00:00 * * @method secondsToTimeOfDay * @returns {String} the formated time string in the format HH:MM:ss */ secondsToTimeOfDay : function(seconds){ var hours = Math.floor(seconds/3600); var minutes = Math.floor(seconds/60)-hours*60; seconds = seconds - (hours * 3600) - (minutes *60); return hours+":"+ ("0" + minutes).slice(-2) +":"+ ("0" + seconds).slice(-2); }, /* * This methods generates a unique ID with the given length or 10 if no length was given. * The method uses all characters from [A-z0-9] but does not guarantuee a unique string. * It's more a pseudo random string. * * @method generateId * @param the length of the returned pseudo random string * @return a random string with the given length */ generateId : function(length) { var id = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < (length ? length : 10); i++) { id += possible.charAt(Math.floor(Math.random() * possible.length)); } return id; }, /* * */ parseLatLonArray : function(latlngs) { var coordinates = new Array(); for ( var i = 0 ; i < latlngs.length ; i++ ) coordinates.push(new r360.Point(latlngs[i][0], latlngs[i][1])) return coordinates; }, /* * This methods uses the Rotue360° geocoding service to return * a street address for a given latitude/longitude coordinate pair. * This functionality is typically called reverse geocoding. * * @method getAddressByCoordinates * @param {Object} [latlon] The coordinate * @param {Number} [latlon.lat] The latitude of the coordinate. * @param {Number} [latlon.lng] The longitude of the coordinate. * @param {String} [language] The country code, 'nb' for norway, 'de' for germany. * @param {Function} [callback] The callback methods which processes the returned data. */ getAddressByCoordinates : function(latlng, language, callback){ $.getJSON(r360.config.nominatimUrl + 'reverse.php?&format=json&lat=' + latlng.lat + '&accept-language=' + language + '&lon=' + latlng.lng + '&json_callback=?', callback); }, /* * This method takes a result from the nominatim reverse geocoder and formats * it to a readable and displayable string. It builds up an address like this: * 'STREETNAME STREETNUMBER, POSTALCODE, CITY' * In case any of these values are undefined, they get removed from returned string. * In case all values are undefined, the 'display_name' property of the returned * json (from nominatim) is used to generate the output value. * @return {String} a string representing the geocoordinates in human readable form */ formatReverseGeocoding : function(json) { var streetAdress = []; if ( r360.has(json.address, 'road') ) streetAdress.push(json.address.road); if ( r360.has(json.address, 'house_number') ) streetAdress.push(json.address.house_number); var city = []; if ( r360.has(json.address, 'postcode') ) city.push(json.address.postcode); if ( r360.has(json.address, 'city') ) city.push(json.address.city); var address = []; if ( streetAdress.length > 0 ) address.push(streetAdress.join(' ')); if ( city.length > 0) address.push(city.join(', ')); if ( streetAdress.length == 0 && city.length == 0 ) address.push(json.display_name); return address.join(', '); }, /** * [formatPhotonReverseGeocoding description] * @param {type} place [description] * @return {type} [description] */ formatPhotonReverseGeocoding : function(place) { var streetAdress = []; if ( r360.has(place, 'name') ) streetAdress.push(place.name); if ( r360.has(place, 'street') ) streetAdress.push(place.street); if ( r360.has(place, 'housenumber') ) streetAdress.push(place.housenumber); var city = []; if ( r360.has(place, 'postcode') ) city.push(place.postcode); if ( r360.has(place, 'city') ) city.push(place.city); var address = []; if ( streetAdress.length > 0 ) address.push(streetAdress.join(' ')); if ( city.length > 0) address.push(city.join(', ')); if ( streetAdress.length == 0 && city.length == 0 ) address.push("Reverse geocoding not possible."); return address.join(', '); }, /* * */ parsePolygons : function(polygonsJson) { var multiPolygon = []; // we get polygons for each source for ( var i = 0 ; i < polygonsJson.length ; i++ ) { var source = polygonsJson[i]; for ( var j = 0; j < source.polygons.length; j++ ) { // get the polygon infos var polygonJson = source.polygons[j]; // create a polygon with the outer boundary as the initial linestring var polygon = r360.polygon(polygonJson.travelTime, polygonJson.area, r360.lineString(r360.Util.parseLatLonArray(polygonJson.outerBoundary))); // set color and default to black of not found var color = r360.findWhere(r360.config.defaultTravelTimeControlOptions.travelTimes, { time : polygon.getTravelTime() }); polygon.setColor(!r360.isUndefined(color) ? color.color : '#000000'); // set opacity and default to 1 if not found var opacity = r360.findWhere(r360.config.defaultTravelTimeControlOptions.travelTimes, { time : polygon.getTravelTime() }) polygon.setOpacity(!r360.isUndefined(opacity) ? opacity.opacity : 1); if ( typeof polygonJson.innerBoundary !== 'undefined' ) { // add all inner linestrings to polygon for ( var k = 0 ; k < polygonJson.innerBoundary.length ; k++ ) polygon.addInnerBoundary(r360.lineString(r360.Util.parseLatLonArray(polygonJson.innerBoundary[k]))); } r360.PolygonUtil.addPolygonToMultiPolygon(multiPolygon, polygon); } } // make sure the multipolygons are sorted by the travel time ascendingly multiPolygon.sort(function(a,b) { return b.getTravelTime() - a.getTravelTime(); }); return multiPolygon; }, /* * This method parses the JSON returned from the Route360 Webservice and generates * java script objects representing the values. */ parseRoutes : function(json){ var routes = new Array(); for(var i = 0; i < json.routes.length; i++){ var meta = json.routes[i]; routes.push(r360.route(json.routes[i].travelTime, json.routes[i].segments, meta)); } return routes; }, // 3857 => pixel webMercatorToLeaflet : function(point){ return r360.CRS.EPSG3857.transformation._transform(r360.point(point.x / 6378137, point.y / 6378137)); }, webMercatorToLatLng : function(point, elevation){ var latlng = r360.CRS.EPSG3857.projection.unproject(new r360.Point(point.x, point.y)); // x,y,z given so we have elevation data if ( typeof elevation !== 'undefined' ) return r360.latLng([latlng.lat, latlng.lng, elevation]); // no elevation given, just unproject coordinates to lat/lng else return latlng; }, latLngToWebMercator : function(latlng){ var point = r360.Projection.SphericalMercator.project(latlng); point.x *= 6378137; point.y *= 6378137; return point; }, getUserAgent : function(){ var ua= navigator.userAgent, tem, M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; if(/trident/i.test(M[1])){ tem= /\brv[ :]+(\d+)/g.exec(ua) || []; return 'IE '+(tem[1] || ''); } if(M[1]=== 'Chrome'){ tem= ua.match(/\bOPR\/(\d+)/) if(tem!= null) return 'Opera '+tem[1]; }