2013-08-06 23:48:25 +08:00
( function ( exports ) {
2013-08-29 21:22:57 +08:00
exports . torque = exports . torque || { } ;
2013-09-11 19:21:33 +08:00
var requestAnimationFrame = window . requestAnimationFrame || window . mozRequestAnimationFrame || window . webkitRequestAnimationFrame || window . msRequestAnimationFrame || function ( c ) { setTimeout ( c , 16 ) ; }
2013-08-06 23:48:25 +08:00
2013-11-23 01:31:04 +08:00
var cancelAnimationFrame = window . requestAnimationFrame || window . mozCancelAnimationFrame || window . webkitRequestAnimationFrame || window . msRequestAnimationFrame || function ( c ) { } ;
2013-08-06 23:48:25 +08:00
/ * *
* options :
* animationDuration in seconds
* animationDelay in seconds
* /
function Animator ( callback , options ) {
if ( ! options . steps ) {
throw new Error ( "steps option missing" )
}
this . options = options ;
this . running = false ;
this . _tick = this . _tick . bind ( this ) ;
this . _t0 = + new Date ( ) ;
this . callback = callback ;
this . _time = 0.0 ;
2013-08-29 21:22:57 +08:00
_ . defaults ( this . options , {
animationDelay : 0 ,
maxDelta : 0.2 ,
loop : true
} ) ;
2013-08-06 23:48:25 +08:00
2013-10-11 17:09:30 +08:00
this . rescale ( ) ;
2013-08-06 23:48:25 +08:00
}
Animator . prototype = {
start : function ( ) {
this . running = true ;
2013-08-29 21:22:57 +08:00
requestAnimationFrame ( this . _tick ) ;
2013-08-06 23:48:25 +08:00
} ,
2013-10-29 01:41:27 +08:00
isRunning : function ( ) {
return this . running ;
} ,
2013-08-06 23:48:25 +08:00
stop : function ( ) {
2013-08-29 21:22:57 +08:00
this . pause ( ) ;
2013-10-11 17:09:30 +08:00
this . time ( 0 ) ;
} ,
// real animation time
time : function ( _ ) {
if ( ! arguments . length ) return this . _time ;
this . _time = _ ;
2013-08-29 21:22:57 +08:00
var t = this . range ( this . domain ( this . _time ) ) ;
this . callback ( t ) ;
} ,
toggle : function ( ) {
if ( this . running ) {
this . pause ( )
} else {
this . start ( )
}
} ,
2013-10-11 17:09:30 +08:00
rescale : function ( ) {
this . domainInv = torque . math . linear ( this . options . animationDelay , this . options . animationDelay + this . options . animationDuration ) ;
this . domain = this . domainInv . invert ( ) ;
this . range = torque . math . linear ( 0 , this . options . steps ) ;
this . rangeInv = this . range . invert ( ) ;
2013-10-29 01:41:27 +08:00
this . time ( this . _time ) ;
2013-10-11 17:09:30 +08:00
return this ;
} ,
duration : function ( _ ) {
if ( ! arguments . length ) return this . options . animationDuration ;
this . options . animationDuration = _ ;
if ( this . time ( ) > _ ) {
this . time ( 0 ) ;
}
2013-10-29 01:41:27 +08:00
this . rescale ( ) ;
2013-10-11 17:09:30 +08:00
return this ;
} ,
2013-10-29 01:41:27 +08:00
steps : function ( _ ) {
this . options . steps = _ ;
return this . rescale ( ) ;
} ,
2013-09-11 19:21:33 +08:00
step : function ( s ) {
if ( arguments . length === 0 ) return this . range ( this . domain ( this . _time ) ) ;
this . _time = this . domainInv ( this . rangeInv ( s ) ) ;
} ,
2013-08-29 21:22:57 +08:00
pause : function ( ) {
this . running = false ;
cancelAnimationFrame ( this . _tick ) ;
2013-08-06 23:48:25 +08:00
} ,
_tick : function ( ) {
var t1 = + new Date ( ) ;
var delta = ( t1 - this . _t0 ) * 0.001 ;
2013-08-29 21:22:57 +08:00
// if delta is really big means the tab lost the focus
// at some point, so limit delta change
2013-09-11 19:21:33 +08:00
delta = Math . min ( this . options . maxDelta , delta ) ;
2013-08-06 23:48:25 +08:00
this . _t0 = t1 ;
this . _time += delta ;
2013-10-11 17:09:30 +08:00
this . time ( this . _time ) ;
if ( this . step ( ) >= this . options . steps ) {
2013-08-29 21:22:57 +08:00
this . _time = 0 ;
}
if ( this . running ) {
requestAnimationFrame ( this . _tick ) ;
}
2013-08-06 23:48:25 +08:00
}
} ;
2013-08-29 21:22:57 +08:00
exports . torque . Animator = Animator ;
2013-08-06 23:48:25 +08:00
} ) ( typeof exports === "undefined" ? this : exports ) ;
2013-08-01 18:04:39 +08:00
( function ( exports ) {
exports . torque = exports . torque || { } ;
var _torque _reference _latest = {
"version" : "1.0.0" ,
"style" : {
"comp-op" : {
"css" : "comp-op" ,
"default-value" : "src-over" ,
"default-meaning" : "add the current layer on top of other layers" ,
"doc" : "Composite operation. This defines how this layer should behave relative to layers atop or below it." ,
"type" : [
"src" , //
"src-over" , //
"dst-over" , //
"src-in" , //
"dst-in" , //
"src-out" , //
"dst-out" , //
"src-atop" , //
"dst-atop" , //
"xor" , //
"darken" , //
"lighten" //
]
}
} ,
2013-11-23 01:31:04 +08:00
"layer" : {
"buffer-size" : {
"default-value" : "0" ,
"type" : "float" ,
"default-meaning" : "No buffer will be used" ,
"doc" : "Extra tolerance around the Layer extent (in pixels) used to when querying and (potentially) clipping the layer data during rendering"
} ,
2013-11-25 20:07:34 +08:00
"-torque-frame-count" : {
2013-11-23 01:31:04 +08:00
"default-value" : "128" ,
"type" : "number" ,
2013-11-25 20:07:34 +08:00
"default-meaning" : "the data is broken into 128 time frames" ,
"doc" : "Number of animation steps/frames used in the animation. If the data contains a fewere number of total frames, the lesser value will be used."
2013-11-23 01:31:04 +08:00
} ,
"-torque-resolution" : {
"default-value" : "2" ,
"type" : "number" ,
"default-meaning" : "" ,
"doc" : "Spatial resolution in pixels. A resolution of 1 means no spatial aggregation of the data. Any other resolution of N results in spatial aggregation into cells of NxN pixels. The value N must be power of 2"
} ,
"-torque-animation-duration" : {
"default-value" : "30" ,
"type" : "number" ,
"default-meaning" : "the animation lasts 30 seconds" ,
"doc" : "Animation duration in seconds"
} ,
"-torque-aggregation-function" : {
"default-value" : "count(cartodb_id)" ,
"type" : "string" ,
"default-meaning" : "the value for each cell is the count of points in that cell" ,
"doc" : "A function used to calculate a value from the aggregate data for each cell. See -torque-resolution"
} ,
"-torque-time-attribute" : {
"default-value" : "time" ,
"type" : "string" ,
"default-meaning" : "the data column in your table that is of a time based type" ,
"doc" : "The table column that contains the time information used create the animation"
} ,
"-torque-data-aggregation" : {
"default-value" : "linear" ,
"type" : [
"cumulative"
] ,
"default-meaning" : "previous values are discarded" ,
"doc" : "A linear animation will discard previous values while a cumulative animation will accumulate them until it restarts"
}
} ,
2013-08-01 18:04:39 +08:00
"symbolizers" : {
"*" : {
"comp-op" : {
"css" : "comp-op" ,
"default-value" : "src-over" ,
"default-meaning" : "add the current layer on top of other layers" ,
"doc" : "Composite operation. This defines how this layer should behave relative to layers atop or below it." ,
"type" : [
"src" , //
"src-over" , //
"dst-over" , //
"src-in" , //
"dst-in" , //
"src-out" , //
"dst-out" , //
"src-atop" , //
"dst-atop" , //
"xor" , //
"darken" , //
"lighten" //
]
} ,
"opacity" : {
"css" : "opacity" ,
"type" : "float" ,
"doc" : "An alpha value for the style (which means an alpha applied to all features in separate buffer and then composited back to main buffer)" ,
"default-value" : 1 ,
"default-meaning" : "no separate buffer will be used and no alpha will be applied to the style after rendering"
}
} ,
2013-08-29 21:22:57 +08:00
"trail" : {
"steps" : {
"css" : "trail-steps" ,
"type" : "float" ,
"default-value" : 1 ,
"default-meaning" : "no trail steps" ,
"doc" : "How many steps of trails are going to be rendered"
}
} ,
2013-08-01 18:04:39 +08:00
"polygon" : {
"fill" : {
"css" : "polygon-fill" ,
"type" : "color" ,
"default-value" : "rgba(128,128,128,1)" ,
"default-meaning" : "gray and fully opaque (alpha = 1), same as rgb(128,128,128)" ,
"doc" : "Fill color to assign to a polygon"
} ,
"fill-opacity" : {
"css" : "polygon-opacity" ,
"type" : "float" ,
"doc" : "The opacity of the polygon" ,
"default-value" : 1 ,
"default-meaning" : "opaque"
}
} ,
"line" : {
"stroke" : {
"css" : "line-color" ,
"default-value" : "rgba(0,0,0,1)" ,
"type" : "color" ,
"default-meaning" : "black and fully opaque (alpha = 1), same as rgb(0,0,0)" ,
"doc" : "The color of a drawn line"
} ,
"stroke-width" : {
"css" : "line-width" ,
"default-value" : 1 ,
"type" : "float" ,
"doc" : "The width of a line in pixels"
} ,
"stroke-opacity" : {
"css" : "line-opacity" ,
"default-value" : 1 ,
"type" : "float" ,
"default-meaning" : "opaque" ,
"doc" : "The opacity of a line"
} ,
"stroke-linejoin" : {
"css" : "line-join" ,
"default-value" : "miter" ,
"type" : [
"miter" ,
"round" ,
"bevel"
] ,
"doc" : "The behavior of lines when joining"
} ,
"stroke-linecap" : {
"css" : "line-cap" ,
"default-value" : "butt" ,
"type" : [
"butt" ,
"round" ,
"square"
] ,
"doc" : "The display of line endings"
}
} ,
"markers" : {
"file" : {
"css" : "marker-file" ,
"doc" : "An SVG file that this marker shows at each placement. If no file is given, the marker will show an ellipse." ,
"default-value" : "" ,
"default-meaning" : "An ellipse or circle, if width equals height" ,
"type" : "uri"
} ,
"opacity" : {
"css" : "marker-opacity" ,
"doc" : "The overall opacity of the marker, if set, overrides both the opacity of both the fill and stroke" ,
"default-value" : 1 ,
"default-meaning" : "The stroke-opacity and fill-opacity will be used" ,
"type" : "float"
} ,
"fill-opacity" : {
"css" : "marker-fill-opacity" ,
"doc" : "The fill opacity of the marker" ,
"default-value" : 1 ,
"default-meaning" : "opaque" ,
"type" : "float"
} ,
"stroke" : {
"css" : "marker-line-color" ,
"doc" : "The color of the stroke around a marker shape." ,
"default-value" : "black" ,
"type" : "color"
} ,
"stroke-width" : {
"css" : "marker-line-width" ,
"doc" : "The width of the stroke around a marker shape, in pixels. This is positioned on the boundary, so high values can cover the area itself." ,
"type" : "float"
} ,
"stroke-opacity" : {
"css" : "marker-line-opacity" ,
"default-value" : 1 ,
"default-meaning" : "opaque" ,
"doc" : "The opacity of a line" ,
"type" : "float"
} ,
"fill" : {
"css" : "marker-fill" ,
"default-value" : "blue" ,
"doc" : "The color of the area of the marker." ,
"type" : "color"
2013-11-23 01:31:04 +08:00
} ,
"marker-type" : {
"css" : "marker-type" ,
"type" : [
"rectangle" ,
"ellipse"
] ,
"default-value" : "ellipse" ,
"doc" : "The default marker-type. If a SVG file is not given as the marker-file parameter, the renderer provides either an rectangle or an ellipse (a circle if height is equal to width)"
2013-08-01 18:04:39 +08:00
}
} ,
"point" : {
"file" : {
"css" : "point-file" ,
"type" : "uri" ,
"required" : false ,
"default-value" : "none" ,
"doc" : "Image file to represent a point"
} ,
"opacity" : {
"css" : "point-opacity" ,
"type" : "float" ,
"default-value" : 1.0 ,
"default-meaning" : "Fully opaque" ,
"doc" : "A value from 0 to 1 to control the opacity of the point"
}
}
} ,
"colors" : {
"aliceblue" : [ 240 , 248 , 255 ] ,
"antiquewhite" : [ 250 , 235 , 215 ] ,
"aqua" : [ 0 , 255 , 255 ] ,
"aquamarine" : [ 127 , 255 , 212 ] ,
"azure" : [ 240 , 255 , 255 ] ,
"beige" : [ 245 , 245 , 220 ] ,
"bisque" : [ 255 , 228 , 196 ] ,
"black" : [ 0 , 0 , 0 ] ,
"blanchedalmond" : [ 255 , 235 , 205 ] ,
"blue" : [ 0 , 0 , 255 ] ,
"blueviolet" : [ 138 , 43 , 226 ] ,
"brown" : [ 165 , 42 , 42 ] ,
"burlywood" : [ 222 , 184 , 135 ] ,
"cadetblue" : [ 95 , 158 , 160 ] ,
"chartreuse" : [ 127 , 255 , 0 ] ,
"chocolate" : [ 210 , 105 , 30 ] ,
"coral" : [ 255 , 127 , 80 ] ,
"cornflowerblue" : [ 100 , 149 , 237 ] ,
"cornsilk" : [ 255 , 248 , 220 ] ,
"crimson" : [ 220 , 20 , 60 ] ,
"cyan" : [ 0 , 255 , 255 ] ,
"darkblue" : [ 0 , 0 , 139 ] ,
"darkcyan" : [ 0 , 139 , 139 ] ,
"darkgoldenrod" : [ 184 , 134 , 11 ] ,
"darkgray" : [ 169 , 169 , 169 ] ,
"darkgreen" : [ 0 , 100 , 0 ] ,
"darkgrey" : [ 169 , 169 , 169 ] ,
"darkkhaki" : [ 189 , 183 , 107 ] ,
"darkmagenta" : [ 139 , 0 , 139 ] ,
"darkolivegreen" : [ 85 , 107 , 47 ] ,
"darkorange" : [ 255 , 140 , 0 ] ,
"darkorchid" : [ 153 , 50 , 204 ] ,
"darkred" : [ 139 , 0 , 0 ] ,
"darksalmon" : [ 233 , 150 , 122 ] ,
"darkseagreen" : [ 143 , 188 , 143 ] ,
"darkslateblue" : [ 72 , 61 , 139 ] ,
"darkslategrey" : [ 47 , 79 , 79 ] ,
"darkturquoise" : [ 0 , 206 , 209 ] ,
"darkviolet" : [ 148 , 0 , 211 ] ,
"deeppink" : [ 255 , 20 , 147 ] ,
"deepskyblue" : [ 0 , 191 , 255 ] ,
"dimgray" : [ 105 , 105 , 105 ] ,
"dimgrey" : [ 105 , 105 , 105 ] ,
"dodgerblue" : [ 30 , 144 , 255 ] ,
"firebrick" : [ 178 , 34 , 34 ] ,
"floralwhite" : [ 255 , 250 , 240 ] ,
"forestgreen" : [ 34 , 139 , 34 ] ,
"fuchsia" : [ 255 , 0 , 255 ] ,
"gainsboro" : [ 220 , 220 , 220 ] ,
"ghostwhite" : [ 248 , 248 , 255 ] ,
"gold" : [ 255 , 215 , 0 ] ,
"goldenrod" : [ 218 , 165 , 32 ] ,
"gray" : [ 128 , 128 , 128 ] ,
"grey" : [ 128 , 128 , 128 ] ,
"green" : [ 0 , 128 , 0 ] ,
"greenyellow" : [ 173 , 255 , 47 ] ,
"honeydew" : [ 240 , 255 , 240 ] ,
"hotpink" : [ 255 , 105 , 180 ] ,
"indianred" : [ 205 , 92 , 92 ] ,
"indigo" : [ 75 , 0 , 130 ] ,
"ivory" : [ 255 , 255 , 240 ] ,
"khaki" : [ 240 , 230 , 140 ] ,
"lavender" : [ 230 , 230 , 250 ] ,
"lavenderblush" : [ 255 , 240 , 245 ] ,
"lawngreen" : [ 124 , 252 , 0 ] ,
"lemonchiffon" : [ 255 , 250 , 205 ] ,
"lightblue" : [ 173 , 216 , 230 ] ,
"lightcoral" : [ 240 , 128 , 128 ] ,
"lightcyan" : [ 224 , 255 , 255 ] ,
"lightgoldenrodyellow" : [ 250 , 250 , 210 ] ,
"lightgray" : [ 211 , 211 , 211 ] ,
"lightgreen" : [ 144 , 238 , 144 ] ,
"lightgrey" : [ 211 , 211 , 211 ] ,
"lightpink" : [ 255 , 182 , 193 ] ,
"lightsalmon" : [ 255 , 160 , 122 ] ,
"lightseagreen" : [ 32 , 178 , 170 ] ,
"lightskyblue" : [ 135 , 206 , 250 ] ,
"lightslategray" : [ 119 , 136 , 153 ] ,
"lightslategrey" : [ 119 , 136 , 153 ] ,
"lightsteelblue" : [ 176 , 196 , 222 ] ,
"lightyellow" : [ 255 , 255 , 224 ] ,
"lime" : [ 0 , 255 , 0 ] ,
"limegreen" : [ 50 , 205 , 50 ] ,
"linen" : [ 250 , 240 , 230 ] ,
"magenta" : [ 255 , 0 , 255 ] ,
"maroon" : [ 128 , 0 , 0 ] ,
"mediumaquamarine" : [ 102 , 205 , 170 ] ,
"mediumblue" : [ 0 , 0 , 205 ] ,
"mediumorchid" : [ 186 , 85 , 211 ] ,
"mediumpurple" : [ 147 , 112 , 219 ] ,
"mediumseagreen" : [ 60 , 179 , 113 ] ,
"mediumslateblue" : [ 123 , 104 , 238 ] ,
"mediumspringgreen" : [ 0 , 250 , 154 ] ,
"mediumturquoise" : [ 72 , 209 , 204 ] ,
"mediumvioletred" : [ 199 , 21 , 133 ] ,
"midnightblue" : [ 25 , 25 , 112 ] ,
"mintcream" : [ 245 , 255 , 250 ] ,
"mistyrose" : [ 255 , 228 , 225 ] ,
"moccasin" : [ 255 , 228 , 181 ] ,
"navajowhite" : [ 255 , 222 , 173 ] ,
"navy" : [ 0 , 0 , 128 ] ,
"oldlace" : [ 253 , 245 , 230 ] ,
"olive" : [ 128 , 128 , 0 ] ,
"olivedrab" : [ 107 , 142 , 35 ] ,
"orange" : [ 255 , 165 , 0 ] ,
"orangered" : [ 255 , 69 , 0 ] ,
"orchid" : [ 218 , 112 , 214 ] ,
"palegoldenrod" : [ 238 , 232 , 170 ] ,
"palegreen" : [ 152 , 251 , 152 ] ,
"paleturquoise" : [ 175 , 238 , 238 ] ,
"palevioletred" : [ 219 , 112 , 147 ] ,
"papayawhip" : [ 255 , 239 , 213 ] ,
"peachpuff" : [ 255 , 218 , 185 ] ,
"peru" : [ 205 , 133 , 63 ] ,
"pink" : [ 255 , 192 , 203 ] ,
"plum" : [ 221 , 160 , 221 ] ,
"powderblue" : [ 176 , 224 , 230 ] ,
"purple" : [ 128 , 0 , 128 ] ,
"red" : [ 255 , 0 , 0 ] ,
"rosybrown" : [ 188 , 143 , 143 ] ,
"royalblue" : [ 65 , 105 , 225 ] ,
"saddlebrown" : [ 139 , 69 , 19 ] ,
"salmon" : [ 250 , 128 , 114 ] ,
"sandybrown" : [ 244 , 164 , 96 ] ,
"seagreen" : [ 46 , 139 , 87 ] ,
"seashell" : [ 255 , 245 , 238 ] ,
"sienna" : [ 160 , 82 , 45 ] ,
"silver" : [ 192 , 192 , 192 ] ,
"skyblue" : [ 135 , 206 , 235 ] ,
"slateblue" : [ 106 , 90 , 205 ] ,
"slategray" : [ 112 , 128 , 144 ] ,
"slategrey" : [ 112 , 128 , 144 ] ,
"snow" : [ 255 , 250 , 250 ] ,
"springgreen" : [ 0 , 255 , 127 ] ,
"steelblue" : [ 70 , 130 , 180 ] ,
"tan" : [ 210 , 180 , 140 ] ,
"teal" : [ 0 , 128 , 128 ] ,
"thistle" : [ 216 , 191 , 216 ] ,
"tomato" : [ 255 , 99 , 71 ] ,
"turquoise" : [ 64 , 224 , 208 ] ,
"violet" : [ 238 , 130 , 238 ] ,
"wheat" : [ 245 , 222 , 179 ] ,
"white" : [ 255 , 255 , 255 ] ,
"whitesmoke" : [ 245 , 245 , 245 ] ,
"yellow" : [ 255 , 255 , 0 ] ,
"yellowgreen" : [ 154 , 205 , 50 ] ,
"transparent" : [ 0 , 0 , 0 , 0 ]
}
} ;
exports . torque [ 'torque-reference' ] = {
version : {
latest : _torque _reference _latest ,
'1.0.0' : _torque _reference _latest
}
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
} ) ( typeof exports === "undefined" ? this : exports ) ;
//
// common functionallity for torque layers
//
( function ( exports ) {
function TorqueLayer ( ) { }
TorqueLayer . prototype = {
} ;
TorqueLayer . optionsFromLayer = function ( mapConfig ) {
var opts = { } ;
if ( ! mapConfig ) return opts ;
var attrs = {
'buffer-size' : 'buffer-size' ,
2013-11-25 20:07:34 +08:00
'-torque-frame-count' : 'steps' ,
2013-11-23 01:31:04 +08:00
'-torque-resolution' : 'resolution' ,
'-torque-animation-duration' : 'animationDuration' ,
'-torque-aggregation-function' : 'countby' ,
'-torque-time-attribute' : 'column' ,
'-torque-data-aggregation' : 'data_aggregation'
} ;
for ( var i in attrs ) {
var v = mapConfig . eval ( i ) ;
if ( v !== undefined ) {
var a = attrs [ i ] ;
opts [ a ] = v ;
}
}
return opts ;
} ;
TorqueLayer . optionsFromCartoCSS = function ( cartocss ) {
var shader = new carto . RendererJS ( ) . render ( cartocss ) ;
var mapConfig = shader . findLayer ( { name : 'Map' } ) ;
return TorqueLayer . optionsFromLayer ( mapConfig ) ;
} ;
exports . torque . common = torque . common || { } ;
exports . torque . common . TorqueLayer = TorqueLayer ;
2013-08-01 18:04:39 +08:00
} ) ( typeof exports === "undefined" ? this : exports ) ;
2013-09-11 19:21:33 +08:00
( function ( exports ) {
exports . torque = exports . torque || { } ;
var Event = { } ;
Event . on = function ( evt , callback ) {
var cb = this . _evt _callbacks = this . _evt _callbacks || { } ;
var l = cb [ evt ] || ( cb [ evt ] = [ ] ) ;
l . push ( callback ) ;
} ;
Event . trigger = function ( evt ) {
var c = this . _evt _callbacks && this . _evt _callbacks [ evt ] ;
for ( var i = 0 ; c && i < c . length ; ++ i ) {
c [ i ] . apply ( this , Array . prototype . slice . call ( arguments , 1 ) ) ;
}
} ;
Event . fire = Event . trigger ;
Event . off = function ( evt , callback ) {
var c = this . _evt _callbacks && this . _evt _callbacks [ evt ] ;
if ( c && ! callback ) {
delete this . _evt _callbacks [ evt ] ;
return this ;
}
var remove = [ ] ;
for ( var i = 0 ; c && i < c . length ; ++ i ) {
if ( c [ i ] === callback ) remove . push ( i ) ;
}
2013-10-29 01:41:27 +08:00
while ( ( i = remove . pop ( ) ) !== undefined ) c . splice ( i , 1 ) ;
} ;
Event . callbacks = function ( evt ) {
return ( this . _evt _callbacks && this . _evt _callbacks [ evt ] ) || [ ] ;
2013-09-11 19:21:33 +08:00
} ;
exports . torque . Event = Event ;
// types
exports . torque . types = {
Uint8Array : typeof ( window [ 'Uint8Array' ] ) !== 'undefined' ? window . Uint8Array : Array ,
Uint32Array : typeof ( window [ 'Uint32Array' ] ) !== 'undefined' ? window . Uint32Array : Array ,
2013-11-05 15:57:40 +08:00
Int32Array : typeof ( window [ 'Int32Array' ] ) !== 'undefined' ? window . Int32Array : Array
} ;
exports . torque . isBrowserSupported = function ( ) {
return ! ! document . createElement ( 'canvas' ) ;
2013-09-11 19:21:33 +08:00
} ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
exports . torque = exports . torque || { } ;
function clamp ( a , b ) {
return function ( t ) {
return Math . max ( Math . min ( t , b ) , a ) ;
} ;
}
function invLinear ( a , b ) {
var c = clamp ( 0 , 1.0 ) ;
return function ( t ) {
return c ( ( t - a ) / ( b - a ) ) ;
} ;
}
function linear ( a , b ) {
var c = clamp ( a , b ) ;
function _linear ( t ) {
return c ( a * ( 1.0 - t ) + t * b ) ;
}
_linear . invert = function ( ) {
return invLinear ( a , b ) ;
} ;
return _linear ;
}
exports . torque . math = {
clamp : clamp ,
linear : linear ,
invLinear : invLinear
} ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
2013-08-01 18:04:39 +08:00
/ *
# metrics profiler
# # timing
` ` `
var timer = Profiler . metric ( 'resource:load' )
time . start ( ) ;
...
time . end ( ) ;
` ` `
# # counters
` ` `
var counter = Profiler . metric ( 'requests' )
counter . inc ( ) ; // 1
counter . inc ( 10 ) ; // 11
counter . dec ( ) // 10
counter . dec ( 10 ) // 0
` ` `
# # Calls per second
` ` `
var fps = Profiler . metric ( 'fps' )
function render ( ) {
fps . mark ( ) ;
}
` ` `
* /
( function ( exports ) {
var MAX _HISTORY = 1024 ;
function Profiler ( ) { }
Profiler . metrics = { } ;
Profiler . get = function ( name ) {
return Profiler . metrics [ name ] || {
max : 0 ,
2013-11-23 01:31:04 +08:00
min : Number . MAX _VALUE ,
2013-08-01 18:04:39 +08:00
avg : 0 ,
total : 0 ,
count : 0 ,
history : typeof ( Float32Array ) !== 'undefined' ? new Float32Array ( MAX _HISTORY ) : [ ]
} ;
} ;
Profiler . new _value = function ( name , value ) {
var t = Profiler . metrics [ name ] = Profiler . get ( name ) ;
t . max = Math . max ( t . max , value ) ;
t . min = Math . min ( t . min , value ) ;
t . total += value ;
++ t . count ;
t . avg = t . total / t . count ;
t . history [ t . count % MAX _HISTORY ] = value ;
2013-07-30 16:45:35 +08:00
} ;
Profiler . print _stats = function ( ) {
2013-08-01 18:04:39 +08:00
for ( k in Profiler . metrics ) {
var t = Profiler . metrics [ k ] ;
console . log ( " === " + k + " === " ) ;
console . log ( " max: " + t . max ) ;
console . log ( " min: " + t . min ) ;
console . log ( " avg: " + t . avg ) ;
console . log ( " count: " + t . count ) ;
console . log ( " total: " + t . total ) ;
}
} ;
function Metric ( name ) {
this . t0 = null ;
this . name = name ;
this . count = 0 ;
}
Metric . prototype = {
//
// start a time measurement
//
start : function ( ) {
this . t0 = + new Date ( ) ;
2013-08-29 21:22:57 +08:00
return this ;
2013-08-01 18:04:39 +08:00
} ,
// elapsed time since start was called
_elapsed : function ( ) {
return + new Date ( ) - this . t0 ;
} ,
//
// finish a time measurement and register it
// ``start`` should be called first, if not this
// function does not take effect
//
end : function ( ) {
if ( this . t0 !== null ) {
Profiler . new _value ( this . name , this . _elapsed ( ) ) ;
this . t0 = null ;
}
} ,
//
// increments the value
// qty: how many, default = 1
//
inc : function ( qty ) {
qty = qty === undefined ? 1 : qty ;
Profiler . new _value ( this . name , Profiler . get ( this . name ) . count + ( qty ? qty : 0 ) ) ;
} ,
//
// decrements the value
// qty: how many, default = 1
//
dec : function ( qty ) {
qty = qty === undefined ? 1 : qty ;
this . inc ( - qty ) ;
} ,
//
// measures how many times per second this function is called
//
mark : function ( ) {
++ this . count ;
if ( this . t0 === null ) {
this . start ( ) ;
return ;
2013-07-30 16:45:35 +08:00
}
2013-08-01 18:04:39 +08:00
var elapsed = this . _elapsed ( ) ;
if ( elapsed > 1 ) {
Profiler . new _value ( this . name , this . count ) ;
this . count = 0 ;
this . start ( ) ;
}
}
2013-07-30 16:45:35 +08:00
} ;
2013-08-01 18:04:39 +08:00
Profiler . metric = function ( name ) {
return new Metric ( name ) ;
2013-07-30 16:45:35 +08:00
} ;
2013-08-01 18:04:39 +08:00
exports . Profiler = Profiler ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
2013-07-30 16:45:35 +08:00
( function ( exports ) {
2013-09-11 19:21:33 +08:00
var torque = exports . torque = exports . torque || { } ;
2013-07-30 16:45:35 +08:00
var providers = exports . torque . providers = exports . torque . providers || { } ;
2013-09-11 19:21:33 +08:00
var Uint8Array = torque . types . Uint8Array ;
var Int32Array = torque . types . Int32Array ;
var Uint32Array = torque . types . Uint32Array ;
2013-07-30 16:45:35 +08:00
// format('hello, {0}', 'rambo') -> "hello, rambo"
function format ( str ) {
for ( var i = 1 ; i < arguments . length ; ++ i ) {
var attrs = arguments [ i ] ;
for ( var attr in attrs ) {
str = str . replace ( RegExp ( '\\{' + attr + '\\}' , 'g' ) , attrs [ attr ] ) ;
}
}
return str ;
}
var json = function ( options ) {
2013-09-30 19:31:27 +08:00
this . _ready = false ;
this . _tileQueue = [ ] ;
this . options = options ;
2013-07-30 16:45:35 +08:00
2013-09-30 19:31:27 +08:00
this . options . is _time = this . options . is _time === undefined ? true : this . options . is _time ;
2013-11-09 00:09:23 +08:00
this . options . tiler _protocol = options . tiler _protocol || 'http' ;
this . options . tiler _domain = options . tiler _domain || 'cartodb.com' ;
this . options . tiler _port = options . tiler _port || 80 ;
2013-09-28 00:56:03 +08:00
2013-11-23 01:31:04 +08:00
if ( this . options . data _aggregation ) {
this . options . cumulative = this . options . data _aggregation === 'cumulative' ;
}
2013-09-30 19:31:27 +08:00
// check options
if ( options . resolution === undefined ) throw new Error ( "resolution should be provided" ) ;
if ( options . steps === undefined ) throw new Error ( "steps should be provided" ) ;
if ( options . start === undefined ) {
this . _fetchKeySpan ( ) ;
} else {
this . _setReady ( true ) ;
}
} ;
2013-09-28 00:56:03 +08:00
2013-09-30 19:31:27 +08:00
json . prototype = {
2013-09-28 00:56:03 +08:00
2013-07-30 16:45:35 +08:00
/ * *
* return the torque tile encoded in an efficient javascript
* structure :
* {
* x : Uint8Array x coordinates in tile reference system , normally from 0 - 255
* y : Uint8Array y coordinates in tile reference system
* Index : Array index to the properties
* }
* /
proccessTile : function ( rows , coord , zoom ) {
2013-09-11 19:21:33 +08:00
var r ;
2013-07-30 16:45:35 +08:00
var x = new Uint8Array ( rows . length ) ;
var y = new Uint8Array ( rows . length ) ;
2013-11-23 01:31:04 +08:00
var prof _mem = Profiler . metric ( 'ProviderJSON:mem' ) ;
var prof _point _count = Profiler . metric ( 'ProviderJSON:point_count' ) ;
var prof _process _time = Profiler . metric ( 'ProviderJSON:process_time' ) . start ( )
2013-09-11 19:21:33 +08:00
2013-07-30 16:45:35 +08:00
// count number of dates
var dates = 0 ;
2013-11-23 01:31:04 +08:00
var maxDateSlots = - 1 ;
2013-09-11 19:21:33 +08:00
for ( r = 0 ; r < rows . length ; ++ r ) {
2013-07-30 16:45:35 +08:00
var row = rows [ r ] ;
2013-09-11 19:21:33 +08:00
dates += row . dates _ _uint16 . length ;
for ( var d = 0 ; d < row . dates _ _uint16 . length ; ++ d ) {
maxDateSlots = Math . max ( maxDateSlots , row . dates _ _uint16 [ d ] ) ;
}
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
if ( this . options . cumulative ) {
dates = ( 1 + maxDateSlots ) * rows . length ;
}
var type = this . options . cumulative ? Uint32Array : Uint8Array ;
2013-07-30 16:45:35 +08:00
// reserve memory for all the dates
2013-09-11 19:21:33 +08:00
var timeIndex = new Int32Array ( maxDateSlots + 1 ) ; //index-size
var timeCount = new Int32Array ( maxDateSlots + 1 ) ;
2013-11-23 01:31:04 +08:00
var renderData = new ( this . options . valueDataType || type ) ( dates ) ;
2013-07-30 16:45:35 +08:00
var renderDataPos = new Uint32Array ( dates ) ;
2013-11-23 01:31:04 +08:00
prof _mem . inc (
4 * maxDateSlots + // timeIndex
4 * maxDateSlots + // timeCount
dates + //renderData
dates * 4
) ; //renderDataPos
prof _point _count . inc ( rows . length ) ;
2013-09-11 19:21:33 +08:00
var rowsPerSlot = { } ;
2013-07-30 16:45:35 +08:00
// precache pixel positions
for ( var r = 0 ; r < rows . length ; ++ r ) {
var row = rows [ r ] ;
2013-09-11 19:21:33 +08:00
x [ r ] = row . x _ _uint8 * this . options . resolution ;
2013-11-23 01:31:04 +08:00
// fix value when it's in the tile EDGE
// TODO: this should be fixed in SQL query
if ( row . y _ _uint8 === - 1 ) {
y [ r ] = 0 ;
} else {
y [ r ] = row . y _ _uint8 * this . options . resolution ;
}
2013-07-30 16:45:35 +08:00
2013-09-11 19:21:33 +08:00
var dates = row . dates _ _uint16 ;
var vals = row . vals _ _uint8 ;
2013-11-23 01:31:04 +08:00
if ( ! this . options . cumulative ) {
for ( var j = 0 , len = dates . length ; j < len ; ++ j ) {
var rr = rowsPerSlot [ dates [ j ] ] || ( rowsPerSlot [ dates [ j ] ] = [ ] ) ;
if ( this . options . cumulative ) {
vals [ j ] += prev _val ;
}
prev _val = vals [ j ] ;
rr . push ( [ r , vals [ j ] ] ) ;
}
} else {
var valByDate = { }
for ( var j = 0 , len = dates . length ; j < len ; ++ j ) {
valByDate [ dates [ j ] ] = vals [ j ] ;
}
var accum = 0 ;
// extend the latest to the end
for ( var j = dates [ 0 ] ; j <= maxDateSlots ; ++ j ) {
var rr = rowsPerSlot [ j ] || ( rowsPerSlot [ j ] = [ ] ) ;
var v = valByDate [ j ] ;
if ( v ) {
accum += v ;
}
rr . push ( [ r , accum ] ) ;
}
/ * v a r l a s t D a t e S l o t = d a t e s [ d a t e s . l e n g t h - 1 ] ;
for ( var j = lastDateSlot + 1 ; j <= maxDateSlots ; ++ j ) {
var rr = rowsPerSlot [ j ] || ( rowsPerSlot [ j ] = [ ] ) ;
rr . push ( [ r , prev _val ] ) ;
}
* /
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
2013-07-30 16:45:35 +08:00
}
// for each timeslot search active buckets
var renderDataIndex = 0 ;
var timeSlotIndex = 0 ;
2013-09-11 19:21:33 +08:00
var i = 0 ;
for ( var i = 0 ; i <= maxDateSlots ; ++ i ) {
2013-07-30 16:45:35 +08:00
var c = 0 ;
var slotRows = rowsPerSlot [ i ]
if ( slotRows ) {
for ( var r = 0 ; r < slotRows . length ; ++ r ) {
var rr = slotRows [ r ] ;
++ c ;
renderDataPos [ renderDataIndex ] = rr [ 0 ]
renderData [ renderDataIndex ] = rr [ 1 ] ;
++ renderDataIndex ;
}
}
timeIndex [ i ] = timeSlotIndex ;
timeCount [ i ] = c ;
timeSlotIndex += c ;
}
2013-11-23 01:31:04 +08:00
prof _process _time . end ( ) ;
2013-07-30 16:45:35 +08:00
return {
x : x ,
y : y ,
2013-11-25 23:36:58 +08:00
z : zoom ,
2013-07-30 16:45:35 +08:00
coord : {
x : coord . x ,
y : coord . y ,
2013-09-11 19:21:33 +08:00
z : zoom
2013-07-30 16:45:35 +08:00
} ,
timeCount : timeCount ,
timeIndex : timeIndex ,
renderDataPos : renderDataPos ,
2013-11-23 01:31:04 +08:00
renderData : renderData ,
maxDate : maxDateSlots
2013-07-30 16:45:35 +08:00
} ;
} ,
2013-11-01 02:07:31 +08:00
_host : function ( ) {
2013-10-11 17:09:30 +08:00
var opts = this . options ;
var port = opts . sql _api _port ;
2013-10-31 21:41:23 +08:00
var domain = ( ( opts . user _name || opts . user ) + '.' + ( opts . sql _api _domain || 'cartodb.com' ) ) + ( port ? ':' + port : '' ) ;
2013-10-11 17:09:30 +08:00
var protocol = opts . sql _api _protocol || 'http' ;
return this . options . url || protocol + '://' + domain + '/api/v2/sql' ;
2013-07-30 16:45:35 +08:00
} ,
2013-11-01 02:07:31 +08:00
url : function ( subhost ) {
var opts = this . options ;
var protocol = opts . sql _api _protocol || 'http' ;
if ( ! this . options . cdn _url ) {
return this . _host ( ) ;
}
var h = protocol + "://" ;
if ( subhost ) {
h += subhost + "." ;
}
var cdn _host = opts . cdn _url ;
if ( ! cdn _host . http && ! cdn _host . https ) {
throw new Error ( "cdn_host should contain http and/or https entries" ) ;
}
2013-11-09 00:09:23 +08:00
h += cdn _host [ protocol ] + "/" + ( opts . user _name || opts . user ) + '/api/v2/sql' ;
2013-11-01 02:07:31 +08:00
return h ;
} ,
_hash : function ( str ) {
var hash = 0 ;
if ( ! str || str . length == 0 ) return hash ;
for ( var i = 0 , l = str . length ; i < l ; ++ i ) {
hash = ( ( ( hash << 5 ) - hash ) + str . charCodeAt ( i ) ) | 0 ;
}
return hash ;
} ,
2013-10-29 01:41:27 +08:00
_extraParams : function ( ) {
if ( this . options . extra _params ) {
var p = [ ] ;
for ( var k in this . options . extra _params ) {
var v = this . options . extra _params [ k ] ;
2013-11-09 00:09:23 +08:00
if ( v ) {
p . push ( k + "=" + encodeURIComponent ( v ) ) ;
}
2013-10-29 01:41:27 +08:00
}
return p . join ( '&' ) ;
}
return null ;
} ,
2013-07-30 16:45:35 +08:00
2013-11-11 23:46:42 +08:00
isHttps : function ( ) {
return this . options . sql _api _protocol && this . options . sql _api _protocol === 'https' ;
} ,
2013-07-30 16:45:35 +08:00
// execute actual query
sql : function ( sql , callback , options ) {
2013-11-09 00:09:23 +08:00
options = options || { } ;
2013-11-01 02:07:31 +08:00
var subdomains = this . options . subdomains || '0123' ;
2013-11-11 23:46:42 +08:00
if ( this . isHttps ( ) ) {
subdomains = [ null ] ; // no subdomain
}
2013-11-09 00:09:23 +08:00
var url ;
if ( options . no _cdn ) {
url = this . _host ( ) ;
} else {
url = this . url ( subdomains [ Math . abs ( this . _hash ( sql ) ) % subdomains . length ] ) ;
}
2013-10-29 01:41:27 +08:00
var extra = this . _extraParams ( ) ;
2013-11-01 02:07:31 +08:00
torque . net . get ( url + "?q=" + encodeURIComponent ( sql ) + ( extra ? "&" + extra : '' ) , function ( data ) {
2013-07-30 16:45:35 +08:00
if ( options . parseJSON ) {
2013-10-29 01:41:27 +08:00
data = JSON . parse ( data && data . responseText ) ;
2013-07-30 16:45:35 +08:00
}
2013-10-29 01:41:27 +08:00
callback && callback ( data ) ;
2013-07-30 16:45:35 +08:00
} ) ;
} ,
getTileData : function ( coord , zoom , callback ) {
if ( ! this . _ready ) {
this . _tileQueue . push ( [ coord , zoom , callback ] ) ;
} else {
this . _getTileData ( coord , zoom , callback ) ;
}
} ,
_setReady : function ( ready ) {
this . _ready = true ;
this . _processQueue ( ) ;
2013-09-30 19:31:27 +08:00
this . options . ready && this . options . ready ( ) ;
2013-07-30 16:45:35 +08:00
} ,
_processQueue : function ( ) {
var item ;
while ( item = this . _tileQueue . pop ( ) ) {
this . _getTileData . apply ( this , item ) ;
}
} ,
/ * *
* ` coord ` object like { x : tilex , y : tiley }
* ` zoom ` quadtree zoom level
* /
_getTileData : function ( coord , zoom , callback ) {
2013-11-23 01:31:04 +08:00
var prof _fetch _time = Profiler . metric ( 'ProviderJSON:tile_fetch_time' ) . start ( )
2013-07-30 16:45:35 +08:00
this . table = this . options . table ;
var numTiles = 1 << zoom ;
var column _conv = this . options . column ;
if ( this . options . is _time ) {
column _conv = format ( "date_part('epoch', {column})" , this . options ) ;
}
var sql = "" +
"WITH " +
"par AS (" +
" SELECT CDB_XYZ_Resolution({zoom})*{resolution} as res" +
2013-11-23 01:31:04 +08:00
", 256/{resolution} as tile_size" +
2013-07-30 16:45:35 +08:00
", CDB_XYZ_Extent({x}, {y}, {zoom}) as ext " +
")," +
"cte AS ( " +
" SELECT ST_SnapToGrid(i.the_geom_webmercator, p.res) g" +
", {countby} c" +
", floor(({column_conv} - {start})/{step}) d" +
2013-10-29 01:41:27 +08:00
" FROM ({_sql}) i, par p " +
2013-07-30 16:45:35 +08:00
" WHERE i.the_geom_webmercator && p.ext " +
" GROUP BY g, d" +
") " +
"" +
2013-11-23 01:31:04 +08:00
"SELECT (st_x(g)-st_xmin(p.ext))/p.res x__uint8, " +
" (st_y(g)-st_ymin(p.ext))/p.res y__uint8," +
2013-07-30 16:45:35 +08:00
" array_agg(c) vals__uint8," +
" array_agg(d) dates__uint16" +
2013-11-23 01:31:04 +08:00
// the tile_size where are needed because the overlaps query in cte subquery includes the points
// in the left and bottom borders of the tile
" FROM cte, par p where (st_y(g)-st_ymin(p.ext))/p.res < tile_size and (st_x(g)-st_xmin(p.ext))/p.res < tile_size GROUP BY x__uint8, y__uint8" ;
2013-07-30 16:45:35 +08:00
var query = format ( sql , this . options , {
zoom : zoom ,
x : coord . x ,
y : coord . y ,
2013-10-29 01:41:27 +08:00
column _conv : column _conv ,
_sql : this . getSQL ( )
2013-07-30 16:45:35 +08:00
} ) ;
var self = this ;
this . sql ( query , function ( data ) {
2013-11-07 22:54:10 +08:00
if ( data ) {
var rows = JSON . parse ( data . responseText ) . rows ;
callback ( self . proccessTile ( rows , coord , zoom ) ) ;
} else {
callback ( null ) ;
}
2013-11-23 01:31:04 +08:00
prof _fetch _time . end ( ) ;
2013-07-30 16:45:35 +08:00
} ) ;
} ,
2013-09-11 19:21:33 +08:00
getKeySpan : function ( ) {
return {
2013-11-08 23:14:52 +08:00
start : this . options . start * 1000 ,
end : this . options . end * 1000 ,
2013-09-11 19:21:33 +08:00
step : this . options . step ,
2013-11-11 23:46:42 +08:00
steps : this . options . steps ,
columnType : this . options . is _time ? 'date' : 'number'
2013-09-11 19:21:33 +08:00
} ;
} ,
2013-10-29 01:41:27 +08:00
setColumn : function ( column , isTime ) {
this . options . column = column ;
this . options . is _time = isTime === undefined ? true : false ;
2013-11-23 01:31:04 +08:00
this . reload ( ) ;
} ,
setResolution : function ( res ) {
this . options . resolution = res ;
} ,
// return true if tiles has been changed
setOptions : function ( opt ) {
var refresh = false ;
if ( opt . resolution !== undefined && opt . resolution !== this . options . resolution ) {
this . options . resolution = opt . resolution ;
refresh = true ;
}
if ( opt . steps !== undefined && opt . steps !== this . options . steps ) {
this . setSteps ( opt . steps , { silent : true } ) ;
refresh = true ;
}
if ( opt . column !== undefined && opt . column !== this . options . column ) {
this . options . column = opt . column ;
refresh = true ;
}
if ( opt . data _aggregation !== undefined ) {
var c = opt . data _aggregation === 'cumulative' ;
if ( this . options . cumulative !== c ) {
this . options . cumulative = c ;
refresh = true ;
}
}
if ( refresh ) this . reload ( ) ;
return refresh ;
} ,
reload : function ( ) {
2013-10-29 01:41:27 +08:00
this . _ready = false ;
this . _fetchKeySpan ( ) ;
} ,
setSQL : function ( sql ) {
if ( this . options . sql != sql ) {
this . options . sql = sql ;
2013-11-23 01:31:04 +08:00
this . reload ( ) ;
2013-10-29 01:41:27 +08:00
}
} ,
2013-11-07 22:54:10 +08:00
getSteps : function ( ) {
return Math . min ( this . options . steps , this . options . data _steps ) ;
} ,
2013-11-23 01:31:04 +08:00
setSteps : function ( steps , opt ) {
opt = opt || { } ;
2013-10-11 17:09:30 +08:00
if ( this . options . steps !== steps ) {
this . options . steps = steps ;
2013-11-07 22:54:10 +08:00
this . options . step = ( this . options . end - this . options . start ) / this . getSteps ( ) ;
this . options . step = this . options . step || 1 ;
2013-11-23 01:31:04 +08:00
if ( ! opt . silent ) this . reload ( ) ;
2013-10-11 17:09:30 +08:00
}
} ,
2013-09-30 19:31:27 +08:00
getBounds : function ( ) {
return this . options . bounds ;
} ,
2013-10-29 01:41:27 +08:00
getSQL : function ( ) {
return this . options . sql || "select * from " + this . options . table ;
} ,
2013-11-09 00:09:23 +08:00
_tilerHost : function ( ) {
var opts = this . options ;
var user = ( opts . user _name || opts . user ) ;
return opts . tiler _protocol +
"://" + ( user ? user + "." : "" ) +
opts . tiler _domain +
( ( opts . tiler _port != "" ) ? ( ":" + opts . tiler _port ) : "" ) ;
} ,
_fetchUpdateAt : function ( callback ) {
var self = this ;
var layergroup = {
"version" : "1.0.1" ,
"stat_tag" : this . options . stat _tag || 'torque' ,
"layers" : [ {
"type" : "cartodb" ,
"options" : {
"cartocss_version" : "2.1.1" ,
"cartocss" : "#layer {}" ,
"sql" : this . getSQL ( )
}
} ]
} ;
var url = this . _tilerHost ( ) + "/tiles/layergroup" ;
var extra = this . _extraParams ( ) ;
2013-11-11 23:46:42 +08:00
// tiler needs map_key instead of api_key
// so replace it
if ( extra ) {
extra = extra . replace ( 'api_key=' , 'map_key=' ) ;
}
url = url +
"?config=" + encodeURIComponent ( JSON . stringify ( layergroup ) ) +
"&callback=?" + ( extra ? "&" + extra : '' ) ;
torque . net . jsonp ( url , function ( data ) {
2013-11-09 00:09:23 +08:00
var query = format ( "select * from ({sql}) __torque_wrap_sql limit 0" , { sql : self . getSQL ( ) } ) ;
self . sql ( query , function ( queryData ) {
2013-11-11 23:46:42 +08:00
if ( data ) {
callback ( {
updated _at : data . last _updated ,
fields : queryData . fields
} ) ;
}
2013-11-09 00:09:23 +08:00
} , { parseJSON : true } ) ;
} ) ;
} ,
2013-07-30 16:45:35 +08:00
//
// the data range could be set by the user though ``start``
// option. It can be fecthed from the table when the start
// is not specified.
//
2013-09-11 19:21:33 +08:00
_fetchKeySpan : function ( ) {
2013-10-29 01:41:27 +08:00
var self = this ;
2013-07-30 16:45:35 +08:00
var max _col , min _col , max _tmpl , min _tmpl ;
2013-11-09 00:09:23 +08:00
this . _fetchUpdateAt ( function ( data ) {
2013-10-29 01:41:27 +08:00
if ( ! data ) return ;
2013-11-01 02:07:31 +08:00
self . options . extra _params = self . options . extra _params || { } ;
2013-11-09 00:09:23 +08:00
self . options . extra _params . last _updated = data . updated _at || 0 ;
2013-10-29 01:41:27 +08:00
self . options . is _time = data . fields [ self . options . column ] . type === 'date' ;
2013-11-07 22:54:10 +08:00
var column _conv = self . options . column ;
2013-10-29 01:41:27 +08:00
if ( self . options . is _time ) {
max _tmpl = "date_part('epoch', max({column}))" ;
min _tmpl = "date_part('epoch', min({column}))" ;
2013-11-07 22:54:10 +08:00
column _conv = format ( "date_part('epoch', {column})" , self . options ) ;
2013-10-29 01:41:27 +08:00
} else {
max _tmpl = "max({column})" ;
min _tmpl = "min({column})" ;
}
max _col = format ( max _tmpl , { column : self . options . column } ) ;
min _col = format ( min _tmpl , { column : self . options . column } ) ;
2013-11-09 00:09:23 +08:00
/ * v a r s q l _ s t a t s = " " +
2013-11-08 23:14:52 +08:00
"WITH summary_groups as ( " +
"WITH summary as ( " +
"select (row_number() over (order by __time_col asc nulls last)+1)/2 as rownum, __time_col " +
"from (select *, {column} as __time_col from ({sql}) __s) __torque_wrap_sql " +
"order by __time_col asc " +
") " +
"SELECT " +
"max(__time_col) OVER(PARTITION BY rownum) - " +
"min(__time_col) OVER(PARTITION BY rownum) diff " +
"FROM summary " +
"), subq as ( " +
2013-11-07 22:54:10 +08:00
" SELECT " +
"st_xmax(st_envelope(st_collect(the_geom))) xmax, " +
"st_ymax(st_envelope(st_collect(the_geom))) ymax, " +
"st_xmin(st_envelope(st_collect(the_geom))) xmin, " +
"st_ymin(st_envelope(st_collect(the_geom))) ymin, " +
"{max_col} max, " +
"{min_col} min FROM ({sql}) __torque_wrap_sql " +
")" +
2013-11-08 23:14:52 +08:00
"SELECT " +
2013-11-07 22:54:10 +08:00
"xmax, xmin, ymax, ymin, a.max as max_date, a.min as min_date, " +
2013-11-08 23:14:52 +08:00
"avg(diff) as diffavg," +
"(a.max - a.min)/avg(diff) as num_steps " +
"FROM summary_groups, subq a " +
2013-11-07 22:54:10 +08:00
"WHERE diff > 0 group by xmax, xmin, ymax, ymin, max_date, min_date" ;
2013-11-09 00:09:23 +08:00
* /
var sql _stats = " SELECT " +
"st_xmax(st_envelope(st_collect(the_geom))) xmax, " +
"st_ymax(st_envelope(st_collect(the_geom))) ymax, " +
"st_xmin(st_envelope(st_collect(the_geom))) xmin, " +
"st_ymin(st_envelope(st_collect(the_geom))) ymin, " +
"count(*) as num_steps, " +
"{max_col} max_date, " +
"{min_col} min_date FROM ({sql}) __torque_wrap_sql " ;
2013-11-07 22:54:10 +08:00
var sql = format ( sql _stats , {
2013-10-29 01:41:27 +08:00
max _col : max _col ,
min _col : min _col ,
2013-11-07 22:54:10 +08:00
column : column _conv ,
2013-10-29 01:41:27 +08:00
sql : self . getSQL ( )
} ) ;
self . sql ( sql , function ( data ) {
//TODO: manage bounds
data = data . rows [ 0 ] ;
2013-11-07 22:54:10 +08:00
self . options . start = data . min _date ;
self . options . end = data . max _date ;
self . options . step = ( data . max _date - data . min _date ) / Math . min ( self . options . steps , data . num _steps >> 0 ) ;
self . options . data _steps = data . num _steps >> 0 ;
// step can't be 0
self . options . step = self . options . step || 1 ;
2013-11-08 23:14:52 +08:00
self . options . bounds = [
2013-10-29 01:41:27 +08:00
[ data . ymin , data . xmin ] ,
2013-11-08 23:14:52 +08:00
[ data . ymax , data . xmax ]
2013-10-29 01:41:27 +08:00
] ;
self . _setReady ( true ) ;
2013-11-09 00:09:23 +08:00
} , { parseJSON : true , no _cdn : true } ) ;
} , { parseJSON : true , no _cdn : true } )
2013-07-30 16:45:35 +08:00
}
} ;
2013-09-11 19:21:33 +08:00
torque . providers . json = json ;
2013-07-30 16:45:35 +08:00
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
2013-09-11 19:21:33 +08:00
var torque = exports . torque = exports . torque || { } ;
2013-07-30 16:45:35 +08:00
var providers = exports . torque . providers = exports . torque . providers || { } ;
2013-09-11 19:21:33 +08:00
var Uint8Array = torque . types . Uint8Array ;
var Int32Array = torque . types . Int32Array ;
var Uint32Array = torque . types . Uint32Array ;
2013-07-30 16:45:35 +08:00
// format('hello, {0}', 'rambo') -> "hello, rambo"
function format ( str , attrs ) {
for ( var i = 1 ; i < arguments . length ; ++ i ) {
var attrs = arguments [ i ] ;
for ( var attr in attrs ) {
str = str . replace ( RegExp ( '\\{' + attr + '\\}' , 'g' ) , attrs [ attr ] ) ;
}
}
return str ;
}
var json = function ( options ) {
// check options
this . options = options ;
} ;
2013-08-01 18:04:39 +08:00
2013-07-30 16:45:35 +08:00
json . prototype = {
2013-08-01 18:04:39 +08:00
//
// return the data aggregated by key:
// {
// key0: 12,
// key1: 32
// key2: 25
// }
//
aggregateByKey : function ( rows ) {
function getKeys ( row ) {
var HEADER _SIZE = 3 ;
var valuesCount = row . data [ 2 ] ;
var keys = { } ;
for ( var s = 0 ; s < valuesCount ; ++ s ) {
keys [ row . data [ HEADER _SIZE + s ] ] = row . data [ HEADER _SIZE + valuesCount + s ] ;
}
return keys ;
}
var keys = { } ;
for ( r = 0 ; r < rows . length ; ++ r ) {
var rowKeys = getKeys ( rows [ r ] ) ;
for ( var k in rowKeys ) {
keys [ k ] = keys [ k ] || 0 ;
keys [ k ] += rowKeys [ k ] ;
}
}
return keys ;
} ,
2013-09-11 19:21:33 +08:00
2013-08-01 18:04:39 +08:00
2013-07-30 16:45:35 +08:00
/ * *
*
* /
proccessTile : function ( rows , coord , zoom ) {
var r ;
var x = new Uint8Array ( rows . length ) ;
var y = new Uint8Array ( rows . length ) ;
var self = this ;
// decode into a javascript strcuture the array
function decode _row ( row ) {
var HEADER _SIZE = 3 ;
var o = {
x : row . data [ 0 ] * self . options . resolution ,
y : row . data [ 1 ] * self . options . resolution ,
valuesCount : row . data [ 2 ] ,
times : [ ] ,
2013-11-05 15:57:40 +08:00
values : [ ]
2013-07-30 16:45:35 +08:00
} ;
for ( var s = 0 ; s < o . valuesCount ; ++ s ) {
o . times . push ( row . data [ HEADER _SIZE + s ] ) ;
o . values . push ( row . data [ HEADER _SIZE + o . valuesCount + s ] ) ;
}
2013-09-11 19:21:33 +08:00
if ( self . options . cumulative ) {
2013-07-30 16:45:35 +08:00
for ( var s = 1 ; s < o . valuesCount ; ++ s ) {
o . values [ s ] += o . values [ s - 1 ] ;
}
}
return o
}
// decode all the rows
for ( r = 0 ; r < rows . length ; ++ r ) {
rows [ r ] = decode _row ( rows [ r ] ) ;
}
// count number of dates
var dates = 0 ;
var maxDateSlots = 0 ;
for ( r = 0 ; r < rows . length ; ++ r ) {
var row = rows [ r ] ;
dates += row . times . length ;
for ( var d = 0 ; d < row . times . length ; ++ d ) {
maxDateSlots = Math . max ( maxDateSlots , row . times [ d ] ) ;
}
}
// reserve memory for all the dates
var timeIndex = new Int32Array ( maxDateSlots + 1 ) ; //index-size
var timeCount = new Int32Array ( maxDateSlots + 1 ) ;
2013-08-29 21:22:57 +08:00
var renderData = new ( this . options . valueDataType || Uint8Array ) ( dates ) ;
2013-07-30 16:45:35 +08:00
var renderDataPos = new Uint32Array ( dates ) ;
var rowsPerSlot = { } ;
// precache pixel positions
for ( var r = 0 ; r < rows . length ; ++ r ) {
var row = rows [ r ] ;
x [ r ] = row . x ;
y [ r ] = row . y ;
var dates = row . times ;
var vals = row . values ;
for ( var j = 0 , len = dates . length ; j < len ; ++ j ) {
var rr = rowsPerSlot [ dates [ j ] ] || ( rowsPerSlot [ dates [ j ] ] = [ ] ) ;
rr . push ( [ r , vals [ j ] ] ) ;
}
}
// for each timeslot search active buckets
var renderDataIndex = 0 ;
var timeSlotIndex = 0 ;
var i = 0 ;
for ( var i = 0 ; i <= maxDateSlots ; ++ i ) {
var c = 0 ;
var slotRows = rowsPerSlot [ i ]
if ( slotRows ) {
for ( var r = 0 ; r < slotRows . length ; ++ r ) {
var rr = slotRows [ r ] ;
++ c ;
renderDataPos [ renderDataIndex ] = rr [ 0 ]
renderData [ renderDataIndex ] = rr [ 1 ] ;
++ renderDataIndex ;
}
}
timeIndex [ i ] = timeSlotIndex ;
timeCount [ i ] = c ;
timeSlotIndex += c ;
}
return {
x : x ,
y : y ,
coord : {
x : coord . x ,
y : coord . y ,
z : zoom
} ,
timeCount : timeCount ,
timeIndex : timeIndex ,
renderDataPos : renderDataPos ,
renderData : renderData
} ;
} ,
url : function ( ) {
return this . options . url ;
} ,
2013-08-01 18:04:39 +08:00
2013-08-29 21:22:57 +08:00
tileUrl : function ( coord , zoom ) {
2013-08-01 18:04:39 +08:00
var template = this . url ( ) ;
2013-08-29 21:22:57 +08:00
var s = ( this . options . subdomains || 'abcd' ) [ ( coord . x + coord . y + zoom ) % 4 ] ;
return template
2013-08-01 18:04:39 +08:00
. replace ( '{x}' , coord . x )
. replace ( '{y}' , coord . y )
2013-08-29 21:22:57 +08:00
. replace ( '{z}' , zoom )
. replace ( '{s}' , s ) ;
} ,
getTile : function ( coord , zoom , callback ) {
var template = this . tileUrl ( coord , zoom ) ;
2013-08-01 18:04:39 +08:00
var self = this ;
var fetchTime = Profiler . metric ( 'jsonarray:fetch time' ) ;
fetchTime . start ( ) ;
torque . net . get ( template , function ( data ) {
fetchTime . end ( ) ;
if ( data ) {
data = JSON . parse ( data . responseText ) ;
}
callback ( data ) ;
} ) ;
} ,
2013-07-30 16:45:35 +08:00
/ * *
* ` coord ` object like { x : tilex , y : tiley }
* ` zoom ` quadtree zoom level
* /
getTileData : function ( coord , zoom , callback ) {
2013-08-29 21:22:57 +08:00
var template = this . tileUrl ( coord , zoom ) ;
2013-07-30 16:45:35 +08:00
var self = this ;
2013-08-01 18:04:39 +08:00
var fetchTime = Profiler . metric ( 'jsonarray:fetch time' ) ;
fetchTime . start ( ) ;
2013-07-30 16:45:35 +08:00
torque . net . get ( template , function ( data ) {
2013-08-01 18:04:39 +08:00
fetchTime . end ( ) ;
2013-07-30 16:45:35 +08:00
var processed = null ;
2013-08-01 18:04:39 +08:00
var processingTime = Profiler . metric ( 'jsonarray:processing time' ) ;
2013-08-29 21:22:57 +08:00
var parsingTime = Profiler . metric ( 'jsonarray:parsing time' ) ;
2013-07-30 16:45:35 +08:00
try {
2013-08-01 18:04:39 +08:00
processingTime . start ( ) ;
2013-08-29 21:22:57 +08:00
parsingTime . start ( ) ;
var rows = JSON . parse ( data . responseText || data . response ) . rows ;
parsingTime . end ( ) ;
2013-07-30 16:45:35 +08:00
processed = self . proccessTile ( rows , coord , zoom ) ;
2013-08-01 18:04:39 +08:00
processingTime . end ( ) ;
2013-07-30 16:45:35 +08:00
} catch ( e ) {
console . error ( "problem parsing JSON on " , coord , zoom ) ;
}
2013-08-29 21:22:57 +08:00
callback ( processed ) ;
2013-07-30 16:45:35 +08:00
} ) ;
}
} ;
torque . providers . JsonArray = json
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
var torque = exports . torque = exports . torque || { } ;
torque . net = torque . net || { } ;
2013-10-29 01:41:27 +08:00
var lastCall = null ;
2013-09-11 19:21:33 +08:00
2013-11-11 23:46:42 +08:00
function jsonp ( url , callback , options ) {
options = options || { timeout : 10000 } ;
var head = document . getElementsByTagName ( 'head' ) [ 0 ] ;
var script = document . createElement ( 'script' ) ;
// function name
var fnName = 'torque_' + Date . now ( ) ;
function clean ( ) {
head . removeChild ( script ) ;
clearTimeout ( timeoutTimer ) ;
delete window [ fnName ] ;
}
window [ fnName ] = function ( ) {
clean ( ) ;
callback . apply ( window , arguments ) ;
} ;
// timeout for errors
var timeoutTimer = setTimeout ( function ( ) {
clean ( ) ;
callback . call ( window , null ) ;
} , options . timeout ) ;
// setup url
url = url . replace ( 'callback=\?' , 'callback=' + fnName ) ;
script . type = 'text/javascript' ;
script . src = url ;
script . async = true ;
// defer the loading because IE9 loads in the same frame the script
// so Loader._script is null
setTimeout ( function ( ) { head . appendChild ( script ) ; } , 0 ) ;
}
2013-11-09 00:09:23 +08:00
function get ( url , callback , options ) {
options = options || {
method : 'GET' ,
data : null
} ;
2013-10-29 01:41:27 +08:00
lastCall = { url : url , callback : callback } ;
2013-09-11 19:21:33 +08:00
var request = XMLHttpRequest ;
// from d3.js
if ( window . XDomainRequest
&& ! ( "withCredentials" in request )
&& /^(http(s)?:)?\/\// . test ( url ) ) request = XDomainRequest ;
2013-11-23 01:31:04 +08:00
2013-09-11 19:21:33 +08:00
var req = new request ( ) ;
2013-11-23 01:31:04 +08:00
req . open ( options . method , url , true ) ;
2013-09-11 19:21:33 +08:00
function respond ( ) {
var status = req . status , result ;
if ( ! status && req . responseText || status >= 200 && status < 300 || status === 304 ) {
callback ( req ) ;
} else {
callback ( null ) ;
2013-07-30 16:45:35 +08:00
}
2013-09-11 19:21:33 +08:00
}
"onload" in req
? req . onload = req . onerror = respond
: req . onreadystatechange = function ( ) { req . readyState > 3 && respond ( ) ; } ;
2013-07-30 16:45:35 +08:00
2013-11-23 01:31:04 +08:00
req . onprogress = function ( ) { } ;
2013-07-30 16:45:35 +08:00
//req.responseType = 'arraybuffer';
2013-11-09 00:09:23 +08:00
if ( options . data ) {
req . setRequestHeader ( "Content-type" , "application/json" ) ;
//req.setRequestHeader("Content-type", "application/x-www-form-urlencoded")
req . setRequestHeader ( "Accept" , "*" ) ;
}
req . send ( options . data ) ;
2013-07-30 16:45:35 +08:00
return req ;
}
2013-11-09 00:09:23 +08:00
function post ( url , data , callback ) {
return get ( url , callback , {
data : data ,
method : "POST"
} ) ;
}
2013-07-30 16:45:35 +08:00
torque . net = {
2013-10-29 01:41:27 +08:00
get : get ,
2013-11-09 00:09:23 +08:00
post : post ,
2013-11-11 23:46:42 +08:00
jsonp : jsonp ,
2013-10-29 01:41:27 +08:00
lastCall : function ( ) { return lastCall ; }
2013-07-30 16:45:35 +08:00
} ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
exports . torque = exports . torque || { } ;
var TAU = Math . PI * 2 ;
function renderPoint ( ctx , st ) {
ctx . fillStyle = st . fillStyle ;
ctx . strokStyle = st . strokStyle ;
2013-10-11 17:09:30 +08:00
var pixel _size = st [ 'point-radius' ] ;
2013-07-30 16:45:35 +08:00
// render a circle
2013-10-11 17:09:30 +08:00
// fill
2013-07-30 16:45:35 +08:00
ctx . beginPath ( ) ;
ctx . arc ( 0 , 0 , pixel _size , 0 , TAU , true , true ) ;
ctx . closePath ( ) ;
2013-10-11 17:09:30 +08:00
if ( st . fillStyle ) {
if ( st . fillOpacity ) {
2013-07-30 16:45:35 +08:00
ctx . globalAlpha = st . fillOpacity ;
}
ctx . fill ( ) ;
}
2013-10-11 17:09:30 +08:00
// stroke
2013-07-30 16:45:35 +08:00
ctx . globalAlpha = 1.0 ;
2013-10-11 17:09:30 +08:00
if ( st . strokeStyle && st . lineWidth ) {
if ( st . strokeOpacity ) {
2013-07-30 16:45:35 +08:00
ctx . globalAlpha = st . strokeOpacity ;
}
2013-10-11 17:09:30 +08:00
if ( st . lineWidth ) {
2013-07-30 16:45:35 +08:00
ctx . lineWidth = st . lineWidth ;
}
ctx . strokeStyle = st . strokeStyle ;
2013-10-11 17:09:30 +08:00
// do not render for alpha = 0
if ( ctx . globalAlpha > 0 ) {
ctx . stroke ( ) ;
}
2013-07-30 16:45:35 +08:00
}
}
2013-11-23 01:31:04 +08:00
function renderRectangle ( ctx , st ) {
ctx . fillStyle = st . fillStyle ;
ctx . strokStyle = st . strokStyle ;
var pixel _size = st [ 'point-radius' ] ;
// fill
if ( st . fillStyle && st . fillOpacity ) {
ctx . globalAlpha = st . fillOpacity ;
}
ctx . fillRect ( 0 , 0 , pixel _size , pixel _size )
// stroke
ctx . globalAlpha = 1.0 ;
if ( st . strokeStyle && st . lineWidth ) {
if ( st . strokeOpacity ) {
ctx . globalAlpha = st . strokeOpacity ;
}
if ( st . lineWidth ) {
ctx . lineWidth = st . lineWidth ;
}
ctx . strokeStyle = st . strokeStyle ;
// do not render for alpha = 0
if ( ctx . globalAlpha > 0 ) {
ctx . strokeRect ( 0 , 0 , pixel _size , pixel _size ) ;
}
}
}
2013-08-01 18:04:39 +08:00
function renderSprite ( ctx , st ) {
var img = st [ 'point-file' ] || st [ 'marker-file' ] ;
var ratio = img . height / img . width ;
var w = st [ 'point-radius' ] || img . width ;
var h = st [ 'point-radius' ] || st [ 'marker-height' ] || w * ratio ;
ctx . drawImage ( img , 0 , 0 , w , h ) ;
}
2013-10-11 17:09:30 +08:00
exports . torque . cartocss = exports . torque . cartocss || { } ;
2013-08-01 18:04:39 +08:00
exports . torque . cartocss = {
renderPoint : renderPoint ,
2013-11-23 01:31:04 +08:00
renderSprite : renderSprite ,
renderRectangle : renderRectangle
2013-08-01 18:04:39 +08:00
} ;
2013-07-30 16:45:35 +08:00
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
exports . torque = exports . torque || { } ;
exports . torque . renderer = exports . torque . renderer || { } ;
var TAU = Math . PI * 2 ;
var DEFAULT _CARTOCSS = [
'#layer {' ,
' marker-fill: #662506;' ,
2013-08-29 21:22:57 +08:00
' marker-width: 4;' ,
2013-07-30 16:45:35 +08:00
' [value > 1] { marker-fill: #FEE391; }' ,
' [value > 2] { marker-fill: #FEC44F; }' ,
' [value > 3] { marker-fill: #FE9929; }' ,
' [value > 4] { marker-fill: #EC7014; }' ,
' [value > 5] { marker-fill: #CC4C02; }' ,
' [value > 6] { marker-fill: #993404; }' ,
' [value > 7] { marker-fill: #662506; }' ,
'}'
] . join ( '\n' ) ;
//
// this renderer just render points depending of the value
//
function PointRenderer ( canvas , options ) {
if ( ! canvas ) {
throw new Error ( "canvas can't be undefined" ) ;
}
this . options = options ;
this . _canvas = canvas ;
this . _ctx = canvas . getContext ( '2d' ) ;
2013-09-11 19:21:33 +08:00
this . _sprites = [ ] ; // sprites per layer
2013-08-29 21:22:57 +08:00
this . _shader = null ;
2013-07-30 16:45:35 +08:00
this . setCartoCSS ( this . options . cartocss || DEFAULT _CARTOCSS ) ;
}
PointRenderer . prototype = {
setCanvas : function ( canvas ) {
this . _canvas = canvas ;
this . _ctx = canvas . getContext ( '2d' ) ;
} ,
//
// sets the cartocss style to render stuff
//
setCartoCSS : function ( cartocss ) {
2013-11-23 01:31:04 +08:00
// clean sprites
this . setShader ( new carto . RendererJS ( ) . render ( cartocss ) ) ;
} ,
setShader : function ( shader ) {
2013-07-30 16:45:35 +08:00
// clean sprites
2013-09-11 19:21:33 +08:00
this . _sprites = [ ] ;
2013-11-23 01:31:04 +08:00
this . _shader = shader ;
2013-07-30 16:45:35 +08:00
} ,
//
// generate sprite based on cartocss style
//
2013-08-29 21:22:57 +08:00
generateSprite : function ( shader , value , shaderVars ) {
var prof = Profiler . metric ( 'PointRenderer:generateSprite' ) . start ( ) ;
var st = shader . getStyle ( 'canvas-2d' , {
2013-07-30 16:45:35 +08:00
value : value
2013-08-29 21:22:57 +08:00
} , shaderVars ) ;
2013-07-30 16:45:35 +08:00
var pointSize = st [ 'point-radius' ] ;
2013-10-11 17:09:30 +08:00
if ( ! pointSize ) {
2013-07-30 16:45:35 +08:00
throw new Error ( "marker-width property should be set" ) ;
}
2013-10-11 17:09:30 +08:00
// take into account the exterior ring to calculate the size
var canvasSize = ( st . lineWidth || 0 ) + pointSize * 2 ;
2013-07-30 16:45:35 +08:00
var canvas = document . createElement ( 'canvas' ) ;
var ctx = canvas . getContext ( '2d' ) ;
2013-10-11 17:09:30 +08:00
ctx . width = canvas . width = Math . ceil ( canvasSize ) ;
ctx . height = canvas . height = Math . ceil ( canvasSize ) ;
ctx . translate ( canvasSize / 2 , canvasSize / 2 ) ;
if ( st [ 'point-file' ] || st [ 'marker-file' ] ) {
2013-08-01 18:04:39 +08:00
torque . cartocss . renderSprite ( ctx , st ) ;
} else {
2013-11-23 01:31:04 +08:00
var mt = st [ 'marker-type' ] ;
if ( mt && mt === 'rectangle' ) {
torque . cartocss . renderRectangle ( ctx , st ) ;
} else {
torque . cartocss . renderPoint ( ctx , st ) ;
}
2013-08-01 18:04:39 +08:00
}
2013-08-29 21:22:57 +08:00
prof . end ( ) ;
2013-07-30 16:45:35 +08:00
return canvas ;
} ,
2013-08-29 21:22:57 +08:00
//
// renders all the layers (and frames for each layer) from cartocss
//
renderTile : function ( tile , key ) {
2013-11-23 01:31:04 +08:00
var layers = this . _shader . getLayers ( ) ;
2013-08-29 21:22:57 +08:00
for ( var i = 0 , n = layers . length ; i < n ; ++ i ) {
var layer = layers [ i ] ;
2013-11-23 01:31:04 +08:00
if ( layer . name ( ) !== "Map" ) {
var sprites = this . _sprites [ i ] || ( this . _sprites [ i ] = { } ) ;
// frames for each layer
for ( var fr = 0 ; fr < layer . frames ( ) . length ; ++ fr ) {
var frame = layer . frames ( ) [ fr ] ;
var fr _sprites = sprites [ frame ] || ( sprites [ frame ] = [ ] ) ;
this . _renderTile ( tile , key - frame , frame , fr _sprites , layer ) ;
}
2013-08-29 21:22:57 +08:00
}
}
} ,
2013-07-30 16:45:35 +08:00
//
// renders a tile in the canvas for key defined in
// the torque tile
//
2013-08-29 21:22:57 +08:00
_renderTile : function ( tile , key , frame _offset , sprites , shader , shaderVars ) {
2013-07-30 16:45:35 +08:00
if ( ! this . _canvas ) return ;
2013-11-23 01:31:04 +08:00
2013-08-29 21:22:57 +08:00
var prof = Profiler . metric ( 'PointRenderer:renderTile' ) . start ( ) ;
2013-07-30 16:45:35 +08:00
var ctx = this . _ctx ;
2013-11-23 01:31:04 +08:00
var blendMode = shader . eval ( 'comp-op' ) || this . options . blendmode ;
if ( blendMode ) {
ctx . globalCompositeOperation = blendMode ;
}
if ( this . options . cumulative && key > tile . maxDate ) {
//TODO: precache because this tile is not going to change
key = tile . maxDate ;
2013-07-30 16:45:35 +08:00
}
2013-11-23 01:31:04 +08:00
var tileMax = this . options . resolution * ( 256 / this . options . resolution - 1 )
var activePixels = tile . timeCount [ key ] ;
2013-07-30 16:45:35 +08:00
if ( activePixels ) {
var pixelIndex = tile . timeIndex [ key ] ;
for ( var p = 0 ; p < activePixels ; ++ p ) {
var posIdx = tile . renderDataPos [ pixelIndex + p ] ;
var c = tile . renderData [ pixelIndex + p ] ;
if ( c ) {
var sp = sprites [ c ] ;
if ( ! sp ) {
2013-11-25 23:36:58 +08:00
sp = sprites [ c ] = this . generateSprite ( shader , c , _ . extend ( { zoom : tile . z , 'frame-offset' : frame _offset } , shaderVars ) ) ;
2013-07-30 16:45:35 +08:00
}
2013-09-28 00:56:03 +08:00
//var x = tile.x[posIdx]*res - (sp.width >> 1);
//var y = (256 - res - res*tile.y[posIdx]) - (sp.height >> 1);
var x = tile . x [ posIdx ] - ( sp . width >> 1 ) ;
2013-11-23 01:31:04 +08:00
var y = tileMax - tile . y [ posIdx ] ; // flip mercator
ctx . drawImage ( sp , x , y - ( sp . height >> 1 ) ) ;
2013-07-30 16:45:35 +08:00
}
}
}
2013-08-29 21:22:57 +08:00
prof . end ( ) ;
2013-10-11 17:09:30 +08:00
} ,
setBlendMode : function ( b ) {
this . options . blendmode = b ;
2013-07-30 16:45:35 +08:00
}
2013-10-11 17:09:30 +08:00
2013-07-30 16:45:35 +08:00
} ;
// exports public api
exports . torque . renderer . Point = PointRenderer ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
exports . torque = exports . torque || { } ;
exports . torque . renderer = exports . torque . renderer || { } ;
2013-08-01 18:04:39 +08:00
var DEFAULT _CARTOCSS = [
'#layer {' ,
' polygon-fill: #FFFF00;' ,
' [value > 10] { polygon-fill: #FFFF00; }' ,
' [value > 100] { polygon-fill: #FFCC00; }' ,
' [value > 1000] { polygon-fill: #FE9929; }' ,
' [value > 10000] { polygon-fill: #FF6600; }' ,
' [value > 100000] { polygon-fill: #FF3300; }' ,
'}'
] . join ( '\n' ) ;
2013-07-30 16:45:35 +08:00
var TAU = Math . PI * 2 ;
//
// this renderer just render points depending of the value
//
function RectanbleRenderer ( canvas , options ) {
this . options = options ;
2013-08-01 18:04:39 +08:00
carto . tree . Reference . set ( torque [ 'torque-reference' ] ) ;
2013-07-30 16:45:35 +08:00
this . setCanvas ( canvas ) ;
2013-08-01 18:04:39 +08:00
this . setCartoCSS ( this . options . cartocss || DEFAULT _CARTOCSS ) ;
2013-07-30 16:45:35 +08:00
}
RectanbleRenderer . prototype = {
2013-08-01 18:04:39 +08:00
//
// sets the cartocss style to render stuff
//
setCartoCSS : function ( cartocss ) {
this . _cartoCssStyle = new carto . RendererJS ( ) . render ( cartocss ) ;
if ( this . _cartoCssStyle . getLayers ( ) . length > 1 ) {
throw new Error ( "only one CartoCSS layer is supported" ) ;
}
this . _shader = this . _cartoCssStyle . getLayers ( ) [ 0 ] . shader ;
} ,
2013-07-30 16:45:35 +08:00
setCanvas : function ( canvas ) {
if ( ! canvas ) return ;
this . _canvas = canvas ;
this . _ctx = canvas . getContext ( '2d' ) ;
} ,
accumulate : function ( tile , keys ) {
2013-08-29 21:22:57 +08:00
var prof = Profiler . metric ( 'RectangleRender:accumulate' ) . start ( ) ;
2013-07-30 16:45:35 +08:00
var x , y , posIdx , p , k , key , activePixels , pixelIndex ;
var res = this . options . resolution ;
var s = 256 / res ;
var accum = new Float32Array ( s * s ) ;
if ( typeof ( keys ) !== 'object' ) {
keys = [ keys ] ;
}
for ( k = 0 ; k < keys . length ; ++ k ) {
key = keys [ k ] ;
activePixels = tile . timeCount [ key ] ;
if ( activePixels ) {
pixelIndex = tile . timeIndex [ key ] ;
for ( p = 0 ; p < activePixels ; ++ p ) {
posIdx = tile . renderDataPos [ pixelIndex + p ] ;
x = tile . x [ posIdx ] / res ;
y = tile . y [ posIdx ] / res ;
accum [ x * s + y ] += tile . renderData [ pixelIndex + p ] ;
}
}
}
2013-08-29 21:22:57 +08:00
prof . end ( ) ;
2013-07-30 16:45:35 +08:00
return accum ;
} ,
renderTileAccum : function ( accum , px , py ) {
2013-08-29 21:22:57 +08:00
var prof = Profiler . metric ( 'RectangleRender:renderTileAccum' ) . start ( ) ;
var color , x , y , alpha ;
2013-07-30 16:45:35 +08:00
var res = this . options . resolution ;
var ctx = this . _ctx ;
var s = ( 256 / res ) | 0 ;
var s2 = s * s ;
var colors = this . _colors ;
2013-08-29 21:22:57 +08:00
if ( this . options . blendmode ) {
ctx . globalCompositeOperation = this . options . blendmode ;
}
var polygon _alpha = this . _shader [ 'polygon-opacity' ] || function ( ) { return 1.0 ; } ;
2013-07-30 16:45:35 +08:00
for ( var i = 0 ; i < s2 ; ++ i ) {
var xy = i ;
var value = accum [ i ] ;
if ( value ) {
2013-08-01 18:04:39 +08:00
x = ( xy / s ) | 0 ;
y = xy % s ;
// by-pass the style generation for improving performance
color = this . _shader [ 'polygon-fill' ] ( { value : value } , { zoom : 0 } ) ;
2013-07-30 16:45:35 +08:00
ctx . fillStyle = color ;
2013-08-29 21:22:57 +08:00
//TODO: each function should have a default value for each
//property defined in the cartocss
alpha = polygon _alpha ( { value : value } , { zoom : 0 } ) ;
if ( alpha === null ) {
alpha = 1.0 ;
}
ctx . globalAlpha = alpha ;
2013-07-30 16:45:35 +08:00
ctx . fillRect ( x * res , 256 - res - y * res , res , res ) ;
}
}
2013-08-29 21:22:57 +08:00
prof . end ( ) ;
2013-07-30 16:45:35 +08:00
} ,
//
// renders a tile in the canvas for key defined in
// the torque tile
//
renderTile : function ( tile , key , px , py ) {
if ( ! this . _canvas ) return ;
var res = this . options . resolution ;
//var prof = Profiler.get('render').start();
var ctx = this . _ctx ;
var colors = this . _colors ;
var activepixels = tile . timeCount [ key ] ;
if ( activepixels ) {
var w = this . _canvas . width ;
var h = this . _canvas . height ;
//var imageData = ctx.getImageData(0, 0, w, h);
//var pixels = imageData.data;
var pixelIndex = tile . timeIndex [ key ] ;
for ( var p = 0 ; p < activePixels ; ++ p ) {
var posIdx = tile . renderDataPos [ pixelIndex + p ] ;
var c = tile . renderData [ pixelIndex + p ] ;
if ( c ) {
var color = colors [ Math . min ( c , colors . length - 1 ) ] ;
var x = tile . x [ posIdx ] ; // + px;
var y = tile . y [ posIdx ] ; //+ py;
ctx . fillStyle = color ;
ctx . fillRect ( x , y , res , res ) ;
/ *
for ( var xx = 0 ; xx < res ; ++ xx ) {
for ( var yy = 0 ; yy < res ; ++ yy ) {
var idx = 4 * ( ( x + xx ) + w * ( y + yy ) ) ;
pixels [ idx + 0 ] = color [ 0 ] ;
pixels [ idx + 1 ] = color [ 1 ] ;
pixels [ idx + 2 ] = color [ 2 ] ;
pixels [ idx + 3 ] = color [ 3 ] ;
}
}
* /
}
}
//ctx.putImageData(imageData, 0, 0);
}
//prof.end();
}
} ;
// exports public api
exports . torque . renderer . Rectangle = RectanbleRenderer ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
/ * *
* @ license
* Copyright 2013 Google Inc . All Rights Reserved .
*
* Licensed under the Apache License , Version 2.0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an "AS IS" BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
/ * *
* @ fileoverview Extends OverlayView to provide a canvas "Layer" .
* @ author Brendan Kenny
* /
/ * *
* A map layer that provides a canvas over the slippy map and a callback
* system for efficient animation . Requires canvas and CSS 2 D transform
* support .
* @ constructor
* @ extends google . maps . OverlayView
* @ param { CanvasLayerOptions = } opt _options Options to set in this CanvasLayer .
* /
if ( typeof ( google ) !== 'undefined' && typeof ( google . maps ) !== 'undefined' ) {
function CanvasLayer ( opt _options ) {
/ * *
* If true , canvas is in a map pane and the OverlayView is fully functional .
* See google . maps . OverlayView . onAdd for more information .
* @ type { boolean }
* @ private
* /
this . isAdded _ = false ;
/ * *
* If true , each update will immediately schedule the next .
* @ type { boolean }
* @ private
* /
this . isAnimated _ = false ;
/ * *
* The name of the MapPane in which this layer will be displayed .
* @ type { string }
* @ private
* /
this . paneName _ = CanvasLayer . DEFAULT _PANE _NAME _ ;
/ * *
* A user - supplied function called whenever an update is required . Null or
* undefined if a callback is not provided .
* @ type { ? function = }
* @ private
* /
this . updateHandler _ = null ;
/ * *
* A user - supplied function called whenever an update is required and the
* map has been resized since the last update . Null or undefined if a
* callback is not provided .
* @ type { ? function }
* @ private
* /
this . resizeHandler _ = null ;
/ * *
* The LatLng coordinate of the top left of the current view of the map . Will
* be null when this . isAdded _ is false .
* @ type { google . maps . LatLng }
* @ private
* /
this . topLeft _ = null ;
/ * *
* The map - pan event listener . Will be null when this . isAdded _ is false . Will
* be null when this . isAdded _ is false .
* @ type { ? function }
* @ private
* /
this . centerListener _ = null ;
/ * *
* The map - resize event listener . Will be null when this . isAdded _ is false .
* @ type { ? function }
* @ private
* /
this . resizeListener _ = null ;
/ * *
* If true , the map size has changed and this . resizeHandler _ must be called
* on the next update .
* @ type { boolean }
* @ private
* /
this . needsResize _ = true ;
/ * *
* A browser - defined id for the currently requested callback . Null when no
* callback is queued .
* @ type { ? number }
* @ private
* /
this . requestAnimationFrameId _ = null ;
var canvas = document . createElement ( 'canvas' ) ;
canvas . style . position = 'absolute' ;
canvas . style . top = 0 ;
canvas . style . left = 0 ;
canvas . style . pointerEvents = 'none' ;
/ * *
* The canvas element .
* @ type { ! HTMLCanvasElement }
* /
this . canvas = canvas ;
/ * *
* Simple bind for functions with no args for bind - less browsers ( Safari ) .
* @ param { Object } thisArg The this value used for the target function .
* @ param { function } func The function to be bound .
* /
function simpleBindShim ( thisArg , func ) {
return function ( ) { func . apply ( thisArg ) ; } ;
}
/ * *
* A reference to this . repositionCanvas _ with this bound as its this value .
* @ type { function }
* @ private
* /
this . repositionFunction _ = simpleBindShim ( this , this . repositionCanvas _ ) ;
/ * *
* A reference to this . resize _ with this bound as its this value .
* @ type { function }
* @ private
* /
this . resizeFunction _ = simpleBindShim ( this , this . resize _ ) ;
/ * *
* A reference to this . update _ with this bound as its this value .
* @ type { function }
* @ private
* /
this . requestUpdateFunction _ = simpleBindShim ( this , this . update _ ) ;
// set provided options, if any
if ( opt _options ) {
this . setOptions ( opt _options ) ;
}
}
CanvasLayer . prototype = new google . maps . OverlayView ( ) ;
/ * *
* The default MapPane to contain the canvas .
* @ type { string }
* @ const
* @ private
* /
2013-10-29 01:41:27 +08:00
CanvasLayer . DEFAULT _PANE _NAME _ = 'overlayLayer' ;
2013-07-30 16:45:35 +08:00
/ * *
* Transform CSS property name , with vendor prefix if required . If browser
* does not support transforms , property will be ignored .
* @ type { string }
* @ const
* @ private
* /
CanvasLayer . CSS _TRANSFORM _ = ( function ( ) {
var div = document . createElement ( 'div' ) ;
var transformProps = [
'transform' ,
'WebkitTransform' ,
'MozTransform' ,
'OTransform' ,
'msTransform'
] ;
for ( var i = 0 ; i < transformProps . length ; i ++ ) {
var prop = transformProps [ i ] ;
if ( div . style [ prop ] !== undefined ) {
return prop ;
}
}
// return unprefixed version by default
return transformProps [ 0 ] ;
} ) ( ) ;
/ * *
* The requestAnimationFrame function , with vendor - prefixed or setTimeout - based
* fallbacks . MUST be called with window as thisArg .
* @ type { function }
* @ param { function } callback The function to add to the frame request queue .
* @ return { number } The browser - defined id for the requested callback .
* @ private
* /
CanvasLayer . prototype . requestAnimFrame _ =
window . requestAnimationFrame ||
window . webkitRequestAnimationFrame ||
window . mozRequestAnimationFrame ||
window . oRequestAnimationFrame ||
window . msRequestAnimationFrame ||
function ( callback ) {
return window . setTimeout ( callback , 1000 / 60 ) ;
} ;
/ * *
* The cancelAnimationFrame function , with vendor - prefixed fallback . Does not
* fall back to clearTimeout as some platforms implement requestAnimationFrame
* but not cancelAnimationFrame , and the cost is an extra frame on onRemove .
* MUST be called with window as thisArg .
* @ type { function }
* @ param { number = } requestId The id of the frame request to cancel .
* @ private
* /
CanvasLayer . prototype . cancelAnimFrame _ =
window . cancelAnimationFrame ||
window . webkitCancelAnimationFrame ||
window . mozCancelAnimationFrame ||
window . oCancelAnimationFrame ||
window . msCancelAnimationFrame ||
function ( requestId ) { } ;
/ * *
* Sets any options provided . See CanvasLayerOptions for more information .
* @ param { CanvasLayerOptions } options The options to set .
* /
CanvasLayer . prototype . setOptions = function ( options ) {
if ( options . animate !== undefined ) {
this . setAnimate ( options . animate ) ;
}
if ( options . paneName !== undefined ) {
this . setPane ( options . paneName ) ;
}
if ( options . updateHandler !== undefined ) {
this . setUpdateHandler ( options . updateHandler ) ;
}
if ( options . resizeHandler !== undefined ) {
this . setResizeHandler ( options . resizeHandler ) ;
}
2013-08-29 21:22:57 +08:00
if ( options . readyHandler ) {
this . readyHandler = options . readyHandler ;
2013-07-30 16:45:35 +08:00
}
2013-08-29 21:22:57 +08:00
2013-07-30 16:45:35 +08:00
} ;
/ * *
* Set the animated state of the layer . If true , updateHandler will be called
* repeatedly , once per frame . If false , updateHandler will only be called when
* a map property changes that could require the canvas content to be redrawn .
* @ param { boolean } animate Whether the canvas is animated .
* /
CanvasLayer . prototype . setAnimate = function ( animate ) {
this . isAnimated _ = ! ! animate ;
if ( this . isAnimated _ ) {
this . scheduleUpdate ( ) ;
}
} ;
/ * *
* @ return { boolean } Whether the canvas is animated .
* /
CanvasLayer . prototype . isAnimated = function ( ) {
return this . isAnimated _ ;
} ;
/ * *
* Set the MapPane in which this layer will be displayed , by name . See
* { @ code google . maps . MapPanes } for the panes available .
* @ param { string } paneName The name of the desired MapPane .
* /
CanvasLayer . prototype . setPaneName = function ( paneName ) {
this . paneName _ = paneName ;
this . setPane _ ( ) ;
} ;
/ * *
* @ return { string } The name of the current container pane .
* /
CanvasLayer . prototype . getPaneName = function ( ) {
return this . paneName _ ;
} ;
/ * *
* Adds the canvas to the specified container pane . Since this is guaranteed to
* execute only after onAdd is called , this is when paneName ' s existence is
* checked ( and an error is thrown if it doesn ' t exist ) .
* @ private
* /
CanvasLayer . prototype . setPane _ = function ( ) {
if ( ! this . isAdded _ ) {
return ;
}
// onAdd has been called, so panes can be used
var panes = this . getPanes ( ) ;
if ( ! panes [ this . paneName _ ] ) {
throw new Error ( '"' + this . paneName _ + '" is not a valid MapPane name.' ) ;
}
panes [ this . paneName _ ] . appendChild ( this . canvas ) ;
} ;
/ * *
* Set a function that will be called whenever the parent map and the overlay ' s
* canvas have been resized . If opt _resizeHandler is null or unspecified , any
* existing callback is removed .
* @ param { ? function = } opt _resizeHandler The resize callback function .
* /
CanvasLayer . prototype . setResizeHandler = function ( opt _resizeHandler ) {
this . resizeHandler _ = opt _resizeHandler ;
} ;
/ * *
* Set a function that will be called when a repaint of the canvas is required .
* If opt _updateHandler is null or unspecified , any existing callback is
* removed .
* @ param { ? function = } opt _updateHandler The update callback function .
* /
CanvasLayer . prototype . setUpdateHandler = function ( opt _updateHandler ) {
this . updateHandler _ = opt _updateHandler ;
} ;
/ * *
* @ inheritDoc
* /
CanvasLayer . prototype . onAdd = function ( ) {
if ( this . isAdded _ ) {
return ;
}
this . isAdded _ = true ;
this . setPane _ ( ) ;
this . resizeListener _ = google . maps . event . addListener ( this . getMap ( ) ,
'resize' , this . resizeFunction _ ) ;
this . centerListener _ = google . maps . event . addListener ( this . getMap ( ) ,
'center_changed' , this . repositionFunction _ ) ;
this . resize _ ( ) ;
this . repositionCanvas _ ( ) ;
2013-08-29 21:22:57 +08:00
this . readyHandler && this . readyHandler ( ) ;
2013-07-30 16:45:35 +08:00
} ;
/ * *
* @ inheritDoc
* /
CanvasLayer . prototype . onRemove = function ( ) {
if ( ! this . isAdded _ ) {
return ;
}
this . isAdded _ = false ;
this . topLeft _ = null ;
// remove canvas and listeners for pan and resize from map
this . canvas . parentElement . removeChild ( this . canvas ) ;
if ( this . centerListener _ ) {
google . maps . event . removeListener ( this . centerListener _ ) ;
this . centerListener _ = null ;
}
if ( this . resizeListener _ ) {
google . maps . event . removeListener ( this . resizeListener _ ) ;
this . resizeListener _ = null ;
}
// cease canvas update callbacks
if ( this . requestAnimationFrameId _ ) {
this . cancelAnimFrame _ . call ( window , this . requestAnimationFrameId _ ) ;
this . requestAnimationFrameId _ = null ;
}
} ;
/ * *
* The internal callback for resize events that resizes the canvas to keep the
* map properly covered .
* @ private
* /
CanvasLayer . prototype . resize _ = function ( ) {
// TODO(bckenny): it's common to use a smaller canvas but use CSS to scale
// what is drawn by the browser to save on fill rate. Add an option to do
// this.
if ( ! this . isAdded _ ) {
return ;
}
var map = this . getMap ( ) ;
var width = map . getDiv ( ) . offsetWidth ;
var height = map . getDiv ( ) . offsetHeight ;
var oldWidth = this . canvas . width ;
var oldHeight = this . canvas . height ;
// resizing may allocate a new back buffer, so do so conservatively
if ( oldWidth !== width || oldHeight !== height ) {
this . canvas . width = width ;
this . canvas . height = height ;
this . canvas . style . width = width + 'px' ;
this . canvas . style . height = height + 'px' ;
this . needsResize _ = true ;
this . scheduleUpdate ( ) ;
}
} ;
/ * *
* @ inheritDoc
* /
CanvasLayer . prototype . draw = function ( ) {
this . repositionCanvas _ ( ) ;
} ;
/ * *
* Internal callback for map view changes . Since the Maps API moves the overlay
* along with the map , this function calculates the opposite translation to
* keep the canvas in place .
* @ private
* /
CanvasLayer . prototype . repositionCanvas _ = function ( ) {
// TODO(bckenny): *should* only be executed on RAF, but in current browsers
// this causes noticeable hitches in map and overlay relative
// positioning.
var bounds = this . getMap ( ) . getBounds ( ) ;
this . topLeft _ = new google . maps . LatLng ( bounds . getNorthEast ( ) . lat ( ) ,
bounds . getSouthWest ( ) . lng ( ) ) ;
// canvas position relative to draggable map's conatainer depends on
// overlayView's projection, not the map's
var projection = this . getProjection ( ) ;
var divTopLeft = projection . fromLatLngToDivPixel ( this . topLeft _ ) ;
2013-09-30 19:31:27 +08:00
// when the zoom level is low, more than one map can be shown in the screen
// so the canvas should be attach to the map with more are in the screen
var mapSize = ( 1 << this . getMap ( ) . getZoom ( ) ) * 256 ;
if ( Math . abs ( divTopLeft . x ) > mapSize ) {
divTopLeft . x -= mapSize ;
}
2013-07-30 16:45:35 +08:00
this . canvas . style [ CanvasLayer . CSS _TRANSFORM _ ] = 'translate(' +
Math . round ( divTopLeft . x ) + 'px,' + Math . round ( divTopLeft . y ) + 'px)' ;
this . scheduleUpdate ( ) ;
} ;
/ * *
* Internal callback that serves as main animation scheduler via
* requestAnimationFrame . Calls resize and update callbacks if set , and
* schedules the next frame if overlay is animated .
* @ private
* /
CanvasLayer . prototype . update _ = function ( ) {
this . requestAnimationFrameId _ = null ;
if ( ! this . isAdded _ ) {
return ;
}
if ( this . isAnimated _ ) {
this . scheduleUpdate ( ) ;
}
if ( this . needsResize _ && this . resizeHandler _ ) {
this . needsResize _ = false ;
this . resizeHandler _ ( ) ;
}
if ( this . updateHandler _ ) {
this . updateHandler _ ( ) ;
}
} ;
/ * *
* A convenience method to get the current LatLng coordinate of the top left of
* the current view of the map .
* @ return { google . maps . LatLng } The top left coordinate .
* /
CanvasLayer . prototype . getTopLeft = function ( ) {
return this . topLeft _ ;
} ;
/ * *
* Schedule a requestAnimationFrame callback to updateHandler . If one is
* already scheduled , there is no effect .
* /
CanvasLayer . prototype . scheduleUpdate = function ( ) {
if ( this . isAdded _ && ! this . requestAnimationFrameId _ ) {
this . requestAnimationFrameId _ =
this . requestAnimFrame _ . call ( window , this . requestUpdateFunction _ ) ;
}
} ;
}
2013-08-29 21:22:57 +08:00
/ *
=== === === === === === ==
canvas setup for drawing tiles
=== === === === === === ==
2013-07-30 16:45:35 +08:00
* /
2013-08-29 21:22:57 +08:00
if ( typeof ( google ) !== 'undefined' && typeof ( google . maps ) !== 'undefined' ) {
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
function CanvasTileLayer ( canvas _setup , render ) {
this . tileSize = new google . maps . Size ( 256 , 256 ) ;
this . maxZoom = 19 ;
this . name = "Tile #s" ;
this . alt = "Canvas tile layer" ;
this . tiles = { } ;
this . canvas _setup = canvas _setup ;
this . render = render ;
if ( ! render ) {
this . render = canvas _setup ;
}
}
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
// create a tile with a canvas element
CanvasTileLayer . prototype . create _tile _canvas = function ( coord , zoom , ownerDocument ) {
// create canvas and reset style
var canvas = ownerDocument . createElement ( 'canvas' ) ;
var hit _canvas = ownerDocument . createElement ( 'canvas' ) ;
canvas . style . border = hit _canvas . style . border = "none" ;
canvas . style . margin = hit _canvas . style . margin = "0" ;
canvas . style . padding = hit _canvas . style . padding = "0" ;
// prepare canvas and context sizes
var ctx = canvas . getContext ( '2d' ) ;
ctx . width = canvas . width = this . tileSize . width ;
ctx . height = canvas . height = this . tileSize . height ;
var hit _ctx = hit _canvas . getContext ( '2d' ) ;
hit _canvas . width = hit _ctx . width = this . tileSize . width ;
hit _canvas . height = hit _ctx . height = this . tileSize . height ;
//set unique id
var tile _id = coord . x + '_' + coord . y + '_' + zoom ;
canvas . setAttribute ( 'id' , tile _id ) ;
hit _canvas . setAttribute ( 'id' , tile _id ) ;
if ( tile _id in this . tiles )
delete this . tiles [ tile _id ] ;
this . tiles [ tile _id ] = { canvas : canvas , ctx : ctx , hit _canvas : hit _canvas , hit _ctx : hit _ctx , coord : coord , zoom : zoom , primitives : null } ;
// custom setup
//if (tile_id == '19295_24654_16'){
if ( this . canvas _setup )
this . canvas _setup ( this . tiles [ tile _id ] , coord , zoom ) ;
//}
return canvas ;
}
CanvasTileLayer . prototype . each = function ( callback ) {
for ( var t in this . tiles ) {
var tile = this . tiles [ t ] ;
callback ( tile ) ;
}
}
CanvasTileLayer . prototype . recreate = function ( ) {
for ( var t in this . tiles ) {
var tile = this . tiles [ t ] ;
this . canvas _setup ( tile , tile . coord , tile . zoom ) ;
}
} ;
CanvasTileLayer . prototype . redraw _tile = function ( tile ) {
this . render ( tile , tile . coord , tile . zoom ) ;
} ;
CanvasTileLayer . prototype . redraw = function ( ) {
for ( var t in this . tiles ) {
var tile = this . tiles [ t ] ;
this . render ( tile , tile . coord , tile . zoom ) ;
}
} ;
// could be called directly...
CanvasTileLayer . prototype . getTile = function ( coord , zoom , ownerDocument ) {
return this . create _tile _canvas ( coord , zoom , ownerDocument ) ;
} ;
CanvasTileLayer . prototype . releaseTile = function ( tile ) {
var id = tile . getAttribute ( 'id' ) ;
delete this . tiles [ id ] ;
} ;
}
( function ( exports ) {
if ( typeof ( google ) === 'undefined' || typeof ( google . maps ) === 'undefined' ) return ;
function GMapsTileLoader ( ) {
}
GMapsTileLoader . prototype = {
_initTileLoader : function ( map , projection ) {
this . _map = map ;
this . _projection = projection ;
2013-09-28 00:56:03 +08:00
this . _tiles = { } ;
2013-08-29 21:22:57 +08:00
this . _tilesToLoad = 0 ;
this . _updateTiles = this . _updateTiles . bind ( this ) ;
2013-10-29 01:41:27 +08:00
this . _listeners = [ ] ;
this . _listeners . push (
google . maps . event . addListener ( this . _map , 'dragend' , this . _updateTiles ) ,
google . maps . event . addListener ( this . _map , 'zoom_changed' , this . _updateTiles )
) ;
2013-08-29 21:22:57 +08:00
this . tileSize = 256 ;
this . _updateTiles ( ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
_removeTileLoader : function ( ) {
2013-10-29 01:41:27 +08:00
for ( var i in this . _listeners ) {
google . maps . event . removeListener ( this . _listeners [ i ] ) ;
}
this . _removeTiles ( ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-09-28 00:56:03 +08:00
_removeTiles : function ( ) {
for ( var key in this . _tiles ) {
this . _removeTile ( key ) ;
}
} ,
2013-10-11 17:09:30 +08:00
_reloadTiles : function ( ) {
this . _removeTiles ( ) ;
this . _updateTiles ( ) ;
} ,
2013-08-29 21:22:57 +08:00
_updateTiles : function ( ) {
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
if ( ! this . _map ) { return ; }
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
var bounds = this . _map . getBounds ( ) ;
var zoom = this . _map . getZoom ( ) ;
var tileSize = this . tileSize ;
var mzoom = ( 1 << zoom ) ;
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
var topLeft = new google . maps . LatLng (
bounds . getNorthEast ( ) . lat ( ) ,
bounds . getSouthWest ( ) . lng ( )
) ;
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
var bottomRigth = new google . maps . LatLng (
bounds . getSouthWest ( ) . lat ( ) ,
bounds . getNorthEast ( ) . lng ( )
) ;
this . _projection = this . _map . getProjection ( ) ;
var divTopLeft = this . _projection . fromLatLngToPoint ( topLeft ) ;
var divBottomRight = this . _projection . fromLatLngToPoint ( bottomRigth ) ;
var nwTilePoint = new google . maps . Point (
Math . floor ( divTopLeft . x * mzoom / tileSize ) ,
Math . floor ( divTopLeft . y * mzoom / tileSize ) ) ,
seTilePoint = new google . maps . Point (
Math . floor ( divBottomRight . x * mzoom / tileSize ) ,
Math . floor ( divBottomRight . y * mzoom / tileSize ) ) ;
this . _addTilesFromCenterOut ( nwTilePoint , seTilePoint ) ;
this . _removeOtherTiles ( nwTilePoint , seTilePoint ) ;
} ,
_removeOtherTiles : function ( nwTilePoint , seTilePoint ) {
var kArr , x , y , key ;
var zoom = this . _map . getZoom ( ) ;
for ( key in this . _tiles ) {
if ( this . _tiles . hasOwnProperty ( key ) ) {
kArr = key . split ( ':' ) ;
x = parseInt ( kArr [ 0 ] , 10 ) ;
y = parseInt ( kArr [ 1 ] , 10 ) ;
z = parseInt ( kArr [ 2 ] , 10 ) ;
// remove tile if it's out of bounds
if ( z !== zoom || x < nwTilePoint . x || x > seTilePoint . x || y < nwTilePoint . y || y > seTilePoint . y ) {
this . _removeTile ( key ) ;
}
}
}
} ,
_removeTile : function ( key ) {
this . onTileRemoved && this . onTileRemoved ( this . _tiles [ key ] ) ;
delete this . _tiles [ key ] ;
} ,
_tileShouldBeLoaded : function ( tilePoint ) {
return ! ( ( tilePoint . x + ':' + tilePoint . y + ':' + tilePoint . zoom ) in this . _tiles ) ;
} ,
_tileLoaded : function ( tilePoint , tileData ) {
this . _tilesToLoad -- ;
this . _tiles [ tilePoint . x + ':' + tilePoint . y + ':' + tilePoint . zoom ] = tileData ;
if ( this . _tilesToLoad === 0 ) {
this . onTilesLoaded && this . onTilesLoaded ( ) ;
}
} ,
getTilePos : function ( tilePoint ) {
2013-09-30 19:31:27 +08:00
var limit = ( 1 << this . _map . getZoom ( ) ) ;
// wrap tile
tilePoint = {
x : ( ( tilePoint . x % limit ) + limit ) % limit ,
y : tilePoint . y
} ;
2013-08-29 21:22:57 +08:00
tilePoint = new google . maps . Point (
tilePoint . x * this . tileSize ,
tilePoint . y * this . tileSize
) ;
var bounds = this . _map . getBounds ( ) ;
var topLeft = new google . maps . LatLng (
bounds . getNorthEast ( ) . lat ( ) ,
bounds . getSouthWest ( ) . lng ( )
) ;
var divTopLeft = this . _map . getProjection ( ) . fromLatLngToPoint ( topLeft ) ;
zoom = ( 1 << this . _map . getZoom ( ) ) ;
divTopLeft . x = divTopLeft . x * zoom ;
divTopLeft . y = divTopLeft . y * zoom ;
return new google . maps . Point (
tilePoint . x - divTopLeft . x ,
tilePoint . y - divTopLeft . y
) ;
} ,
_addTilesFromCenterOut : function ( nwTilePoint , seTilePoint ) {
var queue = [ ] ,
center = new google . maps . Point (
( nwTilePoint . x + seTilePoint . x ) * 0.5 ,
( nwTilePoint . y + seTilePoint . y ) * 0.5
) ,
zoom = this . _map . getZoom ( ) ;
var j , i , point ;
for ( j = nwTilePoint . y ; j <= seTilePoint . y ; j ++ ) {
for ( i = nwTilePoint . x ; i <= seTilePoint . x ; i ++ ) {
point = new google . maps . Point ( i , j ) ;
point . zoom = zoom ;
if ( this . _tileShouldBeLoaded ( point ) ) {
queue . push ( point ) ;
}
}
}
var tilesToLoad = queue . length ;
if ( tilesToLoad === 0 ) { return ; }
function distanceToCenterSq ( point ) {
var dx = point . x - center . x ;
var dy = point . y - center . y ;
return dx * dx + dy * dy ;
}
// load tiles in order of their distance to center
queue . sort ( function ( a , b ) {
return distanceToCenterSq ( a ) - distanceToCenterSq ( b ) ;
} ) ;
this . _tilesToLoad += tilesToLoad ;
if ( this . onTileAdded ) {
for ( i = 0 ; i < tilesToLoad ; i ++ ) {
this . onTileAdded ( queue [ i ] ) ;
}
}
}
}
torque . GMapsTileLoader = GMapsTileLoader ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
( function ( exports ) {
if ( typeof ( google ) === 'undefined' || typeof ( google . maps ) === 'undefined' ) return ;
function GMapsTorqueLayer ( options ) {
var self = this ;
2013-11-05 15:57:40 +08:00
if ( ! torque . isBrowserSupported ( ) ) {
throw new Error ( "browser is not supported by torque" ) ;
}
2013-08-29 21:22:57 +08:00
this . key = 0 ;
2013-11-23 01:31:04 +08:00
this . shader = null ;
2013-09-11 19:21:33 +08:00
this . ready = false ;
2013-08-29 21:22:57 +08:00
this . options = _ . extend ( { } , options ) ;
_ . defaults ( this . options , {
provider : 'sql_api' ,
renderer : 'point' ,
resolution : 2 ,
2013-10-31 21:40:01 +08:00
steps : 100 ,
visible : true
2013-08-29 21:22:57 +08:00
} ) ;
2013-11-23 01:31:04 +08:00
if ( options . cartocss ) {
_ . extend ( this . options ,
torque . common . TorqueLayer . optionsFromCartoCSS ( options . cartocss ) ) ;
}
2013-08-29 21:22:57 +08:00
this . animator = new torque . Animator ( function ( time ) {
var k = time | 0 ;
if ( self . key !== k ) {
self . setKey ( k ) ;
}
} , this . options ) ;
this . play = this . animator . start . bind ( this . animator ) ;
this . stop = this . animator . stop . bind ( this . animator ) ;
this . pause = this . animator . pause . bind ( this . animator ) ;
this . toggle = this . animator . toggle . bind ( this . animator ) ;
2013-10-11 17:09:30 +08:00
this . setDuration = this . animator . duration . bind ( this . animator ) ;
2013-10-29 01:41:27 +08:00
this . isRunning = this . animator . isRunning . bind ( this . animator ) ;
2013-10-11 17:09:30 +08:00
2013-08-29 21:22:57 +08:00
CanvasLayer . call ( this , {
map : this . options . map ,
//resizeHandler: this.redraw,
animate : false ,
updateHandler : this . render ,
readyHandler : this . initialize
} ) ;
}
/ * *
* torque layer
* /
GMapsTorqueLayer . prototype = _ . extend ( { } ,
2013-09-11 19:21:33 +08:00
CanvasLayer . prototype ,
2013-08-29 21:22:57 +08:00
torque . GMapsTileLoader . prototype ,
2013-09-11 19:21:33 +08:00
torque . Event ,
2013-08-29 21:22:57 +08:00
{
providers : {
'sql_api' : torque . providers . json ,
'url_template' : torque . providers . jsonarray
} ,
renderers : {
'point' : torque . renderer . Point ,
'pixel' : torque . renderer . Rectangle
} ,
initialize : function ( ) {
var self = this ;
this . onTileAdded = this . onTileAdded . bind ( this ) ;
2013-10-31 21:40:01 +08:00
this . hidden = ! this . options . visible ;
2013-08-29 21:22:57 +08:00
2013-09-30 19:31:27 +08:00
this . options . ready = function ( ) {
self . fire ( "change:bounds" , {
bounds : self . provider . getBounds ( )
} ) ;
2013-11-11 23:46:42 +08:00
self . animator . steps ( self . provider . getSteps ( ) ) ;
2013-10-29 01:41:27 +08:00
self . animator . rescale ( ) ;
2013-11-07 22:54:10 +08:00
self . fire ( 'change:steps' , {
steps : self . provider . getSteps ( )
} ) ;
2013-10-29 01:41:27 +08:00
self . setKey ( self . key ) ;
2013-09-30 19:31:27 +08:00
} ;
2013-08-29 21:22:57 +08:00
this . provider = new this . providers [ this . options . provider ] ( this . options ) ;
this . renderer = new this . renderers [ this . options . renderer ] ( this . getCanvas ( ) , this . options ) ;
this . _initTileLoader ( this . options . map , this . getProjection ( ) ) ;
2013-11-23 01:31:04 +08:00
if ( this . shader ) {
this . renderer . setShader ( this . shader ) ;
2013-07-30 16:45:35 +08:00
}
} ,
2013-10-31 21:40:01 +08:00
hide : function ( ) {
if ( this . hidden ) return this ;
this . pause ( ) ;
this . clear ( ) ;
this . hidden = true ;
return this ;
} ,
show : function ( ) {
if ( ! this . hidden ) return this ;
this . hidden = false ;
this . play ( ) ;
return this ;
} ,
2013-10-29 01:41:27 +08:00
setSQL : function ( sql ) {
if ( ! this . provider || ! this . provider . setSQL ) {
throw new Error ( "this provider does not support SQL" ) ;
}
this . provider . setSQL ( sql ) ;
this . _reloadTiles ( ) ;
return this ;
} ,
setBlendMode : function ( _ ) {
this . renderer && this . renderer . setBlendMode ( _ ) ;
this . redraw ( ) ;
} ,
2013-10-11 17:09:30 +08:00
setSteps : function ( steps ) {
2013-10-29 01:41:27 +08:00
this . provider && this . provider . setSteps ( steps ) ;
this . animator && this . animator . steps ( steps ) ;
this . _reloadTiles ( ) ;
} ,
setColumn : function ( column , isTime ) {
this . provider && this . provider . setColumn ( column , isTime ) ;
2013-10-11 17:09:30 +08:00
this . _reloadTiles ( ) ;
} ,
2013-10-29 01:41:27 +08:00
getTimeBounds : function ( ) {
return this . provider && this . provider . getKeySpan ( ) ;
} ,
2013-07-30 16:45:35 +08:00
getCanvas : function ( ) {
2013-08-29 21:22:57 +08:00
return this . canvas ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
// for each tile shown on the map request the data
onTileAdded : function ( t ) {
var self = this ;
this . provider . getTileData ( t , t . zoom , function ( tileData ) {
self . _tileLoaded ( t , tileData ) ;
2013-11-11 23:46:42 +08:00
if ( tileData ) {
self . redraw ( ) ;
}
2013-08-29 21:22:57 +08:00
} ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-10-31 21:40:01 +08:00
clear : function ( ) {
var canvas = this . canvas ;
canvas . width = canvas . width ;
} ,
2013-08-29 21:22:57 +08:00
/ * *
* render the selectef key
* don 't call this function directly, it' s called by
* requestAnimationFrame . Use redraw to refresh it
* /
render : function ( ) {
2013-10-31 21:40:01 +08:00
if ( this . hidden ) return ;
2013-08-29 21:22:57 +08:00
var t , tile , pos ;
var canvas = this . canvas ;
canvas . width = canvas . width ;
var ctx = canvas . getContext ( '2d' ) ;
// renders only a "frame"
for ( t in this . _tiles ) {
tile = this . _tiles [ t ] ;
2013-11-11 23:46:42 +08:00
if ( tile ) {
pos = this . getTilePos ( tile . coord ) ;
ctx . setTransform ( 1 , 0 , 0 , 1 , pos . x , pos . y ) ;
this . renderer . renderTile ( tile , this . key , pos . x , pos . y ) ;
}
2013-08-29 21:22:57 +08:00
}
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
/ * *
* set key to be shown . If it ' s a single value
* it renders directly , if it ' s an array it renders
* accumulated
* /
setKey : function ( key ) {
this . key = key ;
2013-09-11 19:21:33 +08:00
this . animator . step ( key ) ;
2013-08-29 21:22:57 +08:00
this . redraw ( ) ;
2013-09-11 19:21:33 +08:00
this . fire ( 'change:time' , { time : this . getTime ( ) , step : this . key } ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
/ * *
* helper function , does the same than ` ` setKey ` ` but only
* accepts scalars .
* /
2013-09-11 19:21:33 +08:00
setStep : function ( time ) {
2013-08-29 21:22:57 +08:00
if ( time === undefined || time . length !== undefined ) {
throw new Error ( "setTime only accept scalars" ) ;
}
this . setKey ( time ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-09-11 19:21:33 +08:00
/ * *
* transform from animation step to Date object
* that contains the animation time
*
* ` ` step ` ` should be between 0 and ` ` steps - 1 ` `
* /
stepToTime : function ( step ) {
if ( ! this . provider ) return 0 ;
var times = this . provider . getKeySpan ( ) ;
2013-11-07 22:54:10 +08:00
var time = times . start + ( times . end - times . start ) * ( step / this . provider . getSteps ( ) ) ;
2013-10-29 01:41:27 +08:00
return new Date ( time ) ;
2013-09-11 19:21:33 +08:00
} ,
/ * *
* returns the animation time defined by the data
* in the defined column . Date object
* /
getTime : function ( ) {
return this . stepToTime ( this . key ) ;
} ,
2013-08-29 21:22:57 +08:00
/ * *
* set the cartocss for the current renderer
* /
setCartoCSS : function ( cartocss ) {
2013-11-23 01:31:04 +08:00
var shader = new carto . RendererJS ( ) . render ( cartocss ) ;
this . shader = shader ;
if ( this . renderer ) {
this . renderer . setShader ( shader ) ;
}
// provider options
var options = torque . common . TorqueLayer . optionsFromLayer ( shader . findLayer ( { name : 'Map' } ) ) ;
if ( this . provider && this . provider . setOptions ( options ) ) {
this . _reloadTiles ( ) ;
}
_ . extend ( this . options , options ) ;
// animator options
if ( options . animationDuration ) {
this . animator . duration ( options . animationDuration ) ;
2013-08-29 21:22:57 +08:00
}
2013-11-23 01:31:04 +08:00
2013-09-11 19:21:33 +08:00
this . redraw ( ) ;
2013-07-30 16:45:35 +08:00
return this ;
} ,
2013-08-29 21:22:57 +08:00
redraw : function ( ) {
this . scheduleUpdate ( ) ;
2013-10-11 17:09:30 +08:00
} ,
onRemove : function ( ) {
CanvasLayer . prototype . onRemove . call ( this ) ;
this . animator . stop ( ) ;
2013-10-29 01:41:27 +08:00
this . _removeTileLoader ( ) ;
2013-08-29 21:22:57 +08:00
}
} ) ;
function GMapsTiledTorqueLayer ( options ) {
this . options = _ . extend ( { } , options ) ;
CanvasTileLayer . call ( this , this . _loadTile . bind ( this ) , this . drawTile . bind ( this ) ) ;
this . initialize ( options ) ;
}
GMapsTiledTorqueLayer . prototype = _ . extend ( { } , CanvasTileLayer . prototype , {
providers : {
'sql_api' : torque . providers . json ,
'url_template' : torque . providers . JsonArray
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
renderers : {
'point' : torque . renderer . Point ,
'pixel' : torque . renderer . Rectangle
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
initialize : function ( options ) {
var self = this ;
this . key = 0 ;
this . options . renderer = this . options . renderer || 'pixel' ;
2013-09-11 19:21:33 +08:00
this . options . provider = this . options . provider || 'sql_api' ;
2013-08-29 21:22:57 +08:00
this . provider = new this . providers [ this . options . provider ] ( options ) ;
this . renderer = new this . renderers [ this . options . renderer ] ( null , options ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
_tileLoaded : function ( tile , tileData ) {
tile . data = tileData ;
this . drawTile ( tile ) ;
} ,
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
_loadTile : function ( tile , coord , zoom ) {
var self = this ;
var limit = 1 << zoom ;
// wrap tile
var wrappedCoord = {
x : ( ( coord . x % limit ) + limit ) % limit ,
y : coord . y
} ;
this . provider . getTileData ( wrappedCoord , zoom , function ( tileData ) {
self . _tileLoaded ( tile , tileData ) ;
} ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
drawTile : function ( tile ) {
var canvas = tile . canvas ;
if ( ! tile . data ) return ;
canvas . width = canvas . width ;
this . renderer . setCanvas ( canvas ) ;
var accum = this . renderer . accumulate ( tile . data , this . key ) ;
this . renderer . renderTileAccum ( accum , 0 , 0 ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
setKey : function ( key ) {
this . key = key ;
this . redraw ( ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-29 21:22:57 +08:00
/ * *
* set the cartocss for the current renderer
* /
setCartoCSS : function ( cartocss ) {
if ( ! this . renderer ) throw new Error ( 'renderer is not valid' ) ;
return this . renderer . setCartoCSS ( cartocss ) ;
2013-07-30 16:45:35 +08:00
}
} ) ;
2013-08-29 21:22:57 +08:00
exports . torque . GMapsTiledTorqueLayer = GMapsTiledTorqueLayer ;
exports . torque . GMapsTorqueLayer = GMapsTorqueLayer ;
} ) ( typeof exports === "undefined" ? this : exports ) ;
2013-09-30 22:56:29 +08:00
if ( typeof ( L ) !== 'undefined' ) {
2013-08-29 21:22:57 +08:00
2013-07-30 16:45:35 +08:00
L . Mixin . TileLoader = {
_initTileLoader : function ( ) {
this . _tiles = { }
this . _tilesToLoad = 0 ;
this . _map . on ( {
'moveend' : this . _updateTiles
} , this ) ;
this . _updateTiles ( ) ;
} ,
_removeTileLoader : function ( ) {
2013-10-29 01:41:27 +08:00
this . _map . off ( {
2013-07-30 16:45:35 +08:00
'moveend' : this . _updateTiles
} , this ) ;
2013-09-28 00:56:03 +08:00
this . _removeTiles ( ) ;
2013-07-30 16:45:35 +08:00
} ,
_updateTiles : function ( ) {
if ( ! this . _map ) { return ; }
var bounds = this . _map . getPixelBounds ( ) ,
zoom = this . _map . getZoom ( ) ,
tileSize = this . options . tileSize ;
if ( zoom > this . options . maxZoom || zoom < this . options . minZoom ) {
return ;
}
var nwTilePoint = new L . Point (
Math . floor ( bounds . min . x / tileSize ) ,
Math . floor ( bounds . min . y / tileSize ) ) ,
seTilePoint = new L . Point (
Math . floor ( bounds . max . x / tileSize ) ,
Math . floor ( bounds . max . y / tileSize ) ) ,
tileBounds = new L . Bounds ( nwTilePoint , seTilePoint ) ;
this . _addTilesFromCenterOut ( tileBounds ) ;
this . _removeOtherTiles ( tileBounds ) ;
} ,
2013-09-28 00:56:03 +08:00
_removeTiles : function ( bounds ) {
for ( var key in this . _tiles ) {
this . _removeTile ( key ) ;
}
} ,
2013-10-11 17:09:30 +08:00
_reloadTiles : function ( ) {
this . _removeTiles ( ) ;
this . _updateTiles ( ) ;
} ,
2013-07-30 16:45:35 +08:00
_removeOtherTiles : function ( bounds ) {
2013-09-11 19:21:33 +08:00
var kArr , x , y , z , key ;
var zoom = this . _map . getZoom ( ) ;
2013-07-30 16:45:35 +08:00
for ( key in this . _tiles ) {
if ( this . _tiles . hasOwnProperty ( key ) ) {
kArr = key . split ( ':' ) ;
x = parseInt ( kArr [ 0 ] , 10 ) ;
y = parseInt ( kArr [ 1 ] , 10 ) ;
2013-09-11 19:21:33 +08:00
z = parseInt ( kArr [ 2 ] , 10 ) ;
2013-07-30 16:45:35 +08:00
// remove tile if it's out of bounds
2013-09-11 19:21:33 +08:00
if ( zoom !== z || x < bounds . min . x || x > bounds . max . x || y < bounds . min . y || y > bounds . max . y ) {
2013-07-30 16:45:35 +08:00
this . _removeTile ( key ) ;
}
}
}
} ,
_removeTile : function ( key ) {
this . fire ( 'tileRemoved' , this . _tiles [ key ] ) ;
delete this . _tiles [ key ] ;
} ,
_tileShouldBeLoaded : function ( tilePoint ) {
return ! ( ( tilePoint . x + ':' + tilePoint . y + ':' + tilePoint . zoom ) in this . _tiles ) ;
} ,
_tileLoaded : function ( tilePoint , tileData ) {
this . _tilesToLoad -- ;
this . _tiles [ tilePoint . x + ':' + tilePoint . y + ':' + tilePoint . zoom ] = tileData ;
if ( this . _tilesToLoad === 0 ) {
this . fire ( "tilesLoaded" ) ;
}
} ,
getTilePos : function ( tilePoint ) {
tilePoint = new L . Point ( tilePoint . x , tilePoint . y ) ;
var origin = this . _map . _getNewTopLeftPoint ( this . _map . getCenter ( ) ) ,
tileSize = this . options . tileSize ;
return tilePoint . multiplyBy ( tileSize ) . subtract ( origin ) ;
} ,
_addTilesFromCenterOut : function ( bounds ) {
var queue = [ ] ,
center = bounds . getCenter ( ) ,
zoom = this . _map . getZoom ( ) ;
var j , i , point ;
for ( j = bounds . min . y ; j <= bounds . max . y ; j ++ ) {
for ( i = bounds . min . x ; i <= bounds . max . x ; i ++ ) {
point = new L . Point ( i , j ) ;
point . zoom = zoom ;
if ( this . _tileShouldBeLoaded ( point ) ) {
queue . push ( point ) ;
}
}
}
var tilesToLoad = queue . length ;
if ( tilesToLoad === 0 ) { return ; }
// load tiles in order of their distance to center
queue . sort ( function ( a , b ) {
return a . distanceTo ( center ) - b . distanceTo ( center ) ;
} ) ;
this . _tilesToLoad += tilesToLoad ;
for ( i = 0 ; i < tilesToLoad ; i ++ ) {
this . fire ( 'tileAdded' , queue [ i ] ) ;
}
}
}
2013-09-30 22:56:29 +08:00
} //L defined
if ( typeof ( L ) !== 'undefined' ) {
2013-08-29 21:22:57 +08:00
/ * *
* full canvas layer implementation for Leaflet
* /
L . CanvasLayer = L . Class . extend ( {
includes : [ L . Mixin . Events , L . Mixin . TileLoader ] ,
options : {
minZoom : 0 ,
maxZoom : 28 ,
tileSize : 256 ,
subdomains : 'abc' ,
errorTileUrl : '' ,
attribution : '' ,
zoomOffset : 0 ,
opacity : 1 ,
unloadInvisibleTiles : L . Browser . mobile ,
updateWhenIdle : L . Browser . mobile ,
tileLoader : false // installs tile loading events
} ,
initialize : function ( options ) {
var self = this ;
//this.project = this._project.bind(this);
this . render = this . render . bind ( this ) ;
L . Util . setOptions ( this , options ) ;
this . _canvas = document . createElement ( 'canvas' ) ;
this . _ctx = this . _canvas . getContext ( '2d' ) ;
var requestAnimationFrame = window . requestAnimationFrame || window . mozRequestAnimationFrame ||
2013-11-23 01:31:04 +08:00
window . webkitRequestAnimationFrame || window . msRequestAnimationFrame || function ( callback ) {
return window . setTimeout ( callback , 1000 / 60 ) ;
} ;
2013-08-29 21:22:57 +08:00
this . requestAnimationFrame = requestAnimationFrame ;
} ,
onAdd : function ( map ) {
this . _map = map ;
this . _staticPane = map . _createPane ( 'leaflet-tile-pane' , map . _container ) ;
this . _staticPane . appendChild ( this . _canvas ) ;
map . on ( {
'viewreset' : this . _reset
//'move': this._render
} , this ) ;
map . on ( 'move' , this . _render , this ) ; //function(){ console.log("a"); }, this);
2013-11-11 23:46:42 +08:00
map . on ( 'resize' , this . _reset , this ) ;
2013-08-29 21:22:57 +08:00
if ( this . options . tileLoader ) {
this . _initTileLoader ( ) ;
}
this . _reset ( ) ;
} ,
getCanvas : function ( ) {
return this . _canvas ;
} ,
draw : function ( ) {
return this . _reset ( ) ;
} ,
onRemove : function ( map ) {
map . _container . removeChild ( this . _staticPane ) ;
map . off ( {
'viewreset' : this . _reset ,
2013-11-11 23:46:42 +08:00
'move' : this . _render ,
'resize' : this . _reset
2013-08-29 21:22:57 +08:00
} , this ) ;
} ,
addTo : function ( map ) {
map . addLayer ( this ) ;
return this ;
} ,
setOpacity : function ( opacity ) {
this . options . opacity = opacity ;
this . _updateOpacity ( ) ;
return this ;
} ,
bringToFront : function ( ) {
return this ;
} ,
bringToBack : function ( ) {
return this ;
} ,
_reset : function ( ) {
var size = this . _map . getSize ( ) ;
this . _canvas . width = size . x ;
this . _canvas . height = size . y ;
this . onResize ( ) ;
this . _render ( ) ;
} ,
/ *
_project : function ( x ) {
var point = this . _map . latLngToLayerPoint ( new L . LatLng ( x [ 1 ] , x [ 0 ] ) ) ;
return [ point . x , point . y ] ;
} ,
* /
_updateOpacity : function ( ) { } ,
_render : function ( ) {
this . requestAnimationFrame . call ( window , this . render ) ;
} ,
redraw : function ( ) {
this . _render ( ) ;
} ,
onResize : function ( ) {
} ,
render : function ( ) {
throw new Error ( 'render function should be implemented' ) ;
}
} ) ;
2013-09-30 22:56:29 +08:00
} //L defined
if ( typeof ( L ) !== 'undefined' ) {
2013-07-30 16:45:35 +08:00
/ * *
* torque layer
* /
L . TorqueLayer = L . CanvasLayer . extend ( {
providers : {
'sql_api' : torque . providers . json ,
'url_template' : torque . providers . jsonarray
} ,
renderers : {
'point' : torque . renderer . Point ,
'pixel' : torque . renderer . Rectangle
} ,
initialize : function ( options ) {
var self = this ;
2013-11-05 15:57:40 +08:00
if ( ! torque . isBrowserSupported ( ) ) {
throw new Error ( "browser is not supported by torque" ) ;
}
2013-07-30 16:45:35 +08:00
options . tileLoader = true ;
this . key = 0 ;
2013-11-23 01:31:04 +08:00
if ( options . cartocss ) {
_ . extend ( options , torque . common . TorqueLayer . optionsFromCartoCSS ( options . cartocss ) ) ;
}
2013-07-30 16:45:35 +08:00
2013-08-29 21:22:57 +08:00
options . resolution = options . resolution || 2 ;
options . steps = options . steps || 100 ;
2013-10-31 21:40:01 +08:00
options . visible = options . visible === undefined ? true : options . visible ;
this . hidden = ! options . visible ;
2013-08-29 21:22:57 +08:00
this . animator = new torque . Animator ( function ( time ) {
var k = time | 0 ;
if ( self . key !== k ) {
self . setKey ( k ) ;
}
} , options ) ;
this . play = this . animator . start . bind ( this . animator ) ;
this . stop = this . animator . stop . bind ( this . animator ) ;
this . pause = this . animator . pause . bind ( this . animator ) ;
this . toggle = this . animator . toggle . bind ( this . animator ) ;
2013-10-11 17:09:30 +08:00
this . setDuration = this . animator . duration . bind ( this . animator ) ;
2013-10-29 01:41:27 +08:00
this . isRunning = this . animator . isRunning . bind ( this . animator ) ;
2013-10-11 17:09:30 +08:00
2013-08-29 21:22:57 +08:00
2013-07-30 16:45:35 +08:00
L . CanvasLayer . prototype . initialize . call ( this , options ) ;
this . options . renderer = this . options . renderer || 'point' ;
2013-09-11 19:21:33 +08:00
this . options . provider = this . options . provider || 'sql_api' ;
2013-07-30 16:45:35 +08:00
2013-11-07 22:54:10 +08:00
options . ready = function ( ) {
self . fire ( "change:bounds" , {
bounds : self . provider . getBounds ( )
} ) ;
self . animator . steps ( self . provider . getSteps ( ) ) ;
self . animator . rescale ( ) ;
self . fire ( 'change:steps' , {
steps : self . provider . getSteps ( )
} ) ;
self . setKey ( self . key ) ;
} ;
2013-07-30 16:45:35 +08:00
this . provider = new this . providers [ this . options . provider ] ( options ) ;
this . renderer = new this . renderers [ this . options . renderer ] ( this . getCanvas ( ) , options ) ;
2013-10-11 17:09:30 +08:00
2013-07-30 16:45:35 +08:00
// for each tile shown on the map request the data
this . on ( 'tileAdded' , function ( t ) {
var tileData = this . provider . getTileData ( t , t . zoom , function ( tileData ) {
self . _tileLoaded ( t , tileData ) ;
2013-11-11 23:46:42 +08:00
if ( tileData ) {
self . redraw ( ) ;
}
2013-07-30 16:45:35 +08:00
} ) ;
} , this ) ;
2013-10-29 01:41:27 +08:00
} ,
2013-11-23 01:31:04 +08:00
2013-11-11 23:46:42 +08:00
onRemove : function ( map ) {
2013-10-29 01:41:27 +08:00
this . _removeTileLoader ( ) ;
2013-11-11 23:46:42 +08:00
L . CanvasLayer . prototype . onRemove . call ( this , map ) ;
2013-10-29 01:41:27 +08:00
} ,
2013-10-31 21:40:01 +08:00
hide : function ( ) {
if ( this . hidden ) return this ;
this . pause ( ) ;
this . clear ( ) ;
this . hidden = true ;
return this ;
} ,
show : function ( ) {
if ( ! this . hidden ) return this ;
this . hidden = false ;
this . play ( ) ;
return this ;
} ,
2013-10-29 01:41:27 +08:00
setSQL : function ( sql ) {
if ( ! this . provider || ! this . provider . setSQL ) {
throw new Error ( "this provider does not support SQL" ) ;
}
this . provider . setSQL ( sql ) ;
this . _reloadTiles ( ) ;
return this ;
} ,
setBlendMode : function ( _ ) {
this . renderer . setBlendMode ( _ ) ;
this . redraw ( ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-10-11 17:09:30 +08:00
setSteps : function ( steps ) {
this . provider . setSteps ( steps ) ;
this . _reloadTiles ( ) ;
} ,
2013-10-29 01:41:27 +08:00
setColumn : function ( column , isTime ) {
this . provider . setColumn ( column , isTime ) ;
this . _reloadTiles ( ) ;
} ,
getTimeBounds : function ( ) {
return this . provider && this . provider . getKeySpan ( ) ;
} ,
2013-10-31 21:40:01 +08:00
clear : function ( ) {
var canvas = this . getCanvas ( ) ;
canvas . width = canvas . width ;
} ,
2013-10-29 01:41:27 +08:00
2013-07-30 16:45:35 +08:00
/ * *
* render the selectef key
* don 't call this function directly, it' s called by
* requestAnimationFrame . Use redraw to refresh it
* /
render : function ( ) {
2013-10-31 21:40:01 +08:00
if ( this . hidden ) return ;
2013-07-30 16:45:35 +08:00
var t , tile , pos ;
var canvas = this . getCanvas ( ) ;
canvas . width = canvas . width ;
var ctx = canvas . getContext ( '2d' ) ;
2013-09-11 19:21:33 +08:00
for ( t in this . _tiles ) {
tile = this . _tiles [ t ] ;
2013-11-11 23:46:42 +08:00
if ( tile ) {
pos = this . getTilePos ( tile . coord ) ;
ctx . setTransform ( 1 , 0 , 0 , 1 , pos . x , pos . y ) ;
this . renderer . renderTile ( tile , this . key , pos . x , pos . y ) ;
}
2013-07-30 16:45:35 +08:00
}
} ,
/ * *
* set key to be shown . If it ' s a single value
* it renders directly , if it ' s an array it renders
* accumulated
* /
setKey : function ( key ) {
this . key = key ;
2013-09-11 19:21:33 +08:00
this . animator . step ( key ) ;
2013-07-30 16:45:35 +08:00
this . redraw ( ) ;
2013-09-11 19:21:33 +08:00
this . fire ( 'change:time' , { time : this . getTime ( ) , step : this . key } ) ;
2013-08-29 21:22:57 +08:00
} ,
/ * *
* helper function , does the same than ` ` setKey ` ` but only
* accepts scalars .
* /
2013-09-11 19:21:33 +08:00
setStep : function ( time ) {
2013-08-29 21:22:57 +08:00
if ( time === undefined || time . length !== undefined ) {
throw new Error ( "setTime only accept scalars" ) ;
}
this . setKey ( time ) ;
} ,
2013-09-11 19:21:33 +08:00
/ * *
* transform from animation step to Date object
* that contains the animation time
*
* ` ` step ` ` should be between 0 and ` ` steps - 1 ` `
* /
stepToTime : function ( step ) {
var times = this . provider . getKeySpan ( ) ;
2013-11-07 22:54:10 +08:00
var time = times . start + ( times . end - times . start ) * ( step / this . provider . getSteps ( ) ) ;
2013-10-29 01:41:27 +08:00
return new Date ( time ) ;
2013-09-11 19:21:33 +08:00
} ,
/ * *
* returns the animation time defined by the data
* in the defined column . Date object
* /
getTime : function ( ) {
return this . stepToTime ( this . key ) ;
} ,
/ * *
* returns an object with the start and end times
* /
getTimeSpan : function ( ) {
var times = this . provider . getKeySpan ( ) ;
} ,
2013-08-29 21:22:57 +08:00
/ * *
* set the cartocss for the current renderer
* /
setCartoCSS : function ( cartocss ) {
if ( ! this . renderer ) throw new Error ( 'renderer is not valid' ) ;
2013-11-23 01:31:04 +08:00
var shader = new carto . RendererJS ( ) . render ( cartocss ) ;
this . renderer . setShader ( shader ) ;
// provider options
var options = torque . common . TorqueLayer . optionsFromLayer ( shader . findLayer ( { name : 'Map' } ) ) ;
if ( this . provider . setOptions ( options ) ) {
this . _reloadTiles ( ) ;
}
_ . extend ( this . options , options ) ;
// animator options
if ( options . animationDuration ) {
this . animator . duration ( options . animationDuration ) ;
}
2013-08-29 21:22:57 +08:00
this . redraw ( ) ;
return this ;
2013-07-30 16:45:35 +08:00
}
} ) ;
L . TiledTorqueLayer = L . TileLayer . Canvas . extend ( {
providers : {
'sql_api' : torque . providers . json ,
'url_template' : torque . providers . JsonArray
} ,
renderers : {
'point' : torque . renderer . Point ,
'pixel' : torque . renderer . Rectangle
} ,
initialize : function ( options ) {
var self = this ;
this . key = 0 ;
options . async = true ;
L . TileLayer . Canvas . prototype . initialize . call ( this , options ) ;
this . options . renderer = this . options . renderer || 'pixel' ;
2013-09-11 19:21:33 +08:00
this . options . provider = this . options . provider || 'sql_api' ;
2013-07-30 16:45:35 +08:00
this . provider = new this . providers [ this . options . provider ] ( options ) ;
this . renderer = new this . renderers [ this . options . renderer ] ( null , options ) ;
} ,
_tileLoaded : function ( tile , tilePoint , tileData ) {
2013-08-01 18:04:39 +08:00
if ( this . _tiles [ tilePoint . x + ':' + tilePoint . y ] !== undefined ) {
this . _tiles [ tilePoint . x + ':' + tilePoint . y ] . data = tileData ;
this . drawTile ( tile ) ;
}
2013-08-07 00:18:55 +08:00
L . TileLayer . Canvas . prototype . _tileLoaded . call ( this ) ;
2013-07-30 16:45:35 +08:00
} ,
2013-08-06 23:48:25 +08:00
redraw : function ( ) {
for ( var i in this . _tiles ) {
this . _redrawTile ( this . _tiles [ i ] ) ;
}
} ,
2013-07-30 16:45:35 +08:00
_loadTile : function ( tile , tilePoint ) {
var self = this ;
L . TileLayer . Canvas . prototype . _loadTile . apply ( this , arguments ) ;
2013-08-29 21:22:57 +08:00
// get the data from adjusted point but render in the right canvas
var adjusted = tilePoint . clone ( )
this . _adjustTilePoint ( adjusted ) ;
this . provider . getTileData ( adjusted , this . _map . getZoom ( ) , function ( tileData ) {
2013-07-30 16:45:35 +08:00
self . _tileLoaded ( tile , tilePoint , tileData ) ;
L . DomUtil . addClass ( tile , 'leaflet-tile-loaded' ) ;
} ) ;
} ,
drawTile : function ( tile ) {
var canvas = tile ;
if ( ! tile . data ) return ;
canvas . width = canvas . width ;
this . renderer . setCanvas ( canvas ) ;
var accum = this . renderer . accumulate ( tile . data , this . key ) ;
this . renderer . renderTileAccum ( accum , 0 , 0 ) ;
} ,
setKey : function ( key ) {
this . key = key ;
this . redraw ( ) ;
2013-08-29 21:22:57 +08:00
} ,
/ * *
* set the cartocss for the current renderer
* /
setCartoCSS : function ( cartocss ) {
if ( ! this . renderer ) throw new Error ( 'renderer is not valid' ) ;
return this . renderer . setCartoCSS ( cartocss ) ;
2013-07-30 16:45:35 +08:00
}
} ) ;
2013-09-30 22:56:29 +08:00
} //L defined