├── .gitignore ├── README.md ├── basic-maps └── layer-selector │ └── index.html ├── double-map-alt ├── aww-rats.html ├── css │ ├── dark-theme.css │ ├── light-theme.css │ └── makeitresponsive.css └── index.html ├── intensity-time └── index.html ├── live-map └── index.html ├── manhattanhenge ├── city │ ├── chicago.html │ ├── dc.html │ ├── nyc.html │ └── sf.html ├── img │ ├── bk.png │ ├── chicago.png │ ├── dc.png │ ├── nyc.png │ ├── nyc_districts.png │ ├── pt3.png │ ├── sf.png │ └── veknik_osm_london.png ├── index.html ├── js │ ├── app.js │ ├── carto.js │ ├── cartodb.provider.js │ ├── cartodb.sql.js │ ├── core.js │ ├── geojson.provider.js │ ├── geometry.js │ ├── mercator.js │ ├── model.js │ ├── projector.worker.js │ ├── renderer.js │ ├── settings.js │ ├── shader.js │ ├── vecnik.leatlet.js │ └── vecnik.modestmaps.js ├── libs │ ├── carto.js │ ├── modestmaps.js │ ├── suncalc.js │ └── underscore.js ├── src │ ├── core.js │ ├── geo.js │ ├── latlng.js │ ├── map.js │ ├── math.js │ ├── mercator.js │ ├── renderer.canvas.js │ ├── shader.js │ ├── tween.js │ └── vec2.js ├── today.html └── year.html ├── point-clustering └── index.html ├── private-maps ├── index.html └── style.css ├── scroll-story ├── basic │ ├── css │ │ ├── dark-theme.css │ │ ├── light-theme.css │ │ └── makeitresponsive.css │ ├── index.html │ └── js │ │ ├── VideoOverlay.js │ │ ├── app copy 2.js │ │ └── app.js └── pluto │ ├── css │ ├── makeitresponsive.css │ └── pluto-theme.css │ └── index.html ├── security-definer ├── README.md ├── row-level │ ├── MultiTable.md │ ├── README.md │ └── examples │ │ ├── multi-table-security.html │ │ └── row-level-security.html └── table-level │ └── README.md ├── smart-markers ├── icon-background.png └── index.html ├── tornado ├── README.md ├── index.html └── torque │ ├── css │ ├── leaflet.css │ ├── leaflet.ie.css │ └── style.css │ ├── index.html │ ├── js │ ├── app.js │ ├── canvas_layer.js │ ├── leaflet_tileloader_mixin.js │ ├── map.js │ ├── particles.js │ ├── probs_density_layer.js │ ├── sprites.js │ ├── street_density.js │ ├── time_layer.js │ ├── torque-hm-worker.js │ ├── util.js │ └── vendor │ │ ├── dat.gui.min.js │ │ └── leaflet.js │ ├── scss │ └── style.scss │ └── tests │ ├── d3.min.js │ └── index.html ├── tree-graph └── index.html ├── videomap ├── html5 │ └── index.html ├── inset │ ├── CanvasOverlay.js │ └── index.html ├── video-inset │ ├── VideoOverlay.js │ └── index.html └── vimeo │ └── index.html └── writable ├── function.sql ├── index.html └── polygon-size.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CartoDB Examples 2 | 3 | Contact [@andrewxhill](http://twitter.com/andrewxhill) 4 | 5 | #### CartoDB maps with slide layout 6 | 7 | These are maps I created to highlight the PLUTO data shortly after its release by the NYC government. It ended up being the first of my experiments to get a ton of press: [Gizmodo](http://gizmodo.com/see-nyc-from-a-new-angle-with-these-awesomely-nerdy-map-1093545954), [Visual.ly Blog](http://blog.visual.ly/visualizing-nycs-mappluto-database/), [AnimalNewYork](http://animalnewyork.com/2013/this-nyc-open-data-map-is-mind-bogglingly-comprehensive/), [Gothamist](http://gothamist.com/2013/08/10/geek_out_with_awesome_digital_maps.php), [Wired Maps Lab](http://www.wired.com/wiredscience/2013/08/nyc-pluto-data-map-party/), [Curbed](http://ny.curbed.com/archives/2013/08/09/boring_new_york_city_tax_data_makes_for_nonboring_maps.php), [Brokelyn](http://brokelyn.com/new-project-takes-boring-tax-data-and-turns-it-into-cool-maps-about-nyc/), and [Atlantic Cities](http://www.theatlanticcities.com/technology/2013/08/visual-proof-geographic-data-really-should-be-free/6529/) 8 | 9 | - [demo](http://andrewxhill.github.io/cartodb-examples/scroll-story/pluto/index.html) 10 | 11 | #### Scrolling stories with CartoDB templates and multiple maps 12 | 13 | A summary of work I did at the NYPL historical map data hackathon. I just used a scrolling story layout to talk about the maps I created over an afternoon. 14 | 15 | - [demo](http://andrewxhill.github.io/cartodb-examples/scroll-story/basic/index.html) 16 | 17 | Yea! This got a little love from [Wired](http://www.wired.com/wiredscience/2013/10/phone-map-game-new-york-city/) 18 | 19 | #### Sun position plus street orientation 20 | 21 | Demo I put together of the VECNIK vector rendering with current sunposition to find streets getting a lot of sun exposure 22 | 23 | - [demo](http://andrewxhill.github.io/cartodb-examples/manhattanhenge/today.html) 24 | 25 | or taking a look at places where Manhattanhenge happens for any day of the year, 26 | 27 | - [demo](http://andrewxhill.github.io/cartodb-examples/manhattanhenge/year.html) 28 | 29 | see a couple of other cities, 30 | 31 | - [demo](http://andrewxhill.github.io/cartodb-examples/manhattanhenge/index.html) 32 | 33 | This was built into a full [fledged project](http://nychenge.com) that was run by [WNYC](http://www.wnyc.org/articles/wnyc-news/2013/jul/12/yes-manhattanhenge-also-park-slopehenge/) 34 | 35 | #### Share your private maps 36 | 37 | For when you have collaborators that you trust, a lot, but don't trust them enough not to mess up tables in your CartoDB account. Using this page, you can give them your account name, api_key (and trust them not to share it or leave it laying around!), table and they can view the maps you made. I'm improving the example to add support for the basemaps you select, infowindows, etc., but it will take a couple of weeks to update. 38 | 39 | - [demo](http://andrewxhill.github.io/cartodb-examples/private-maps/index.html) 40 | 41 | #### Two maps, one center 42 | 43 | This is a really quick mod of the CartoDB double map template. In the [orignal template](http://cartodb.github.io/cartodb-publishing-templates/doublemap/), both maps have the same center, and moving one map moves the other. A question on our forum from [Michael Keller](https://twitter.com/mhkeller) prompted me to make this version, where there is only a single center, so one map bleeds into the other. Hacky hacky 44 | 45 | - [demo](http://andrewxhill.github.io/cartodb-examples/double-map-alt/index.html) 46 | 47 | #### Smart markers 48 | 49 | Often times users will want to create a marker layer where the markers come from CartoDB. If you have a large dataset, this can get annoying, dealing with overlaps and not loading too much data at once. In this example, I create a leaflet marker layer limited to the current zoom and bounding box. Within the view, I limit it to one marker per ever 40px square. With every pan and zoom I query new points to fill the space. 50 | 51 | - [demo](http://andrewxhill.github.io/cartodb-examples/smart-markers/index.html) 52 | 53 | #### Writable Polygon 54 | 55 | This example uses PostgreSQL to turn CartoDB into a form submit endpoint without any proxy layer. 56 | 57 | - [code](http://github.com/andrewxhill/cartodb-examples/blob/master/writable) 58 | - [demo](http://andrewxhill.github.io/cartodb-examples/writable/index.html) 59 | 60 | #### Changing polygon intensity over time [D3] 61 | 62 | This example uses the CartoDB SQL API and D3 to show a changing intensity in state polygons over time. 63 | 64 | - [code](http://github.com/andrewxhill/cartodb-examples/blob/master/intensity-time) 65 | - [demo](http://andrewxhill.github.io/cartodb-examples/intensity-time/index.html) 66 | 67 | #### Experiments with Videos, CartoDB, and Leaflet 68 | 69 | This example makes a video's playing timestamp into a control for a map 70 | 71 | - [demo](http://andrewxhill.github.io/cartodb-examples/videomap/html5/index.html) 72 | 73 | Quick test to create a Leaflet plugin for a VideoOVerlay object 74 | 75 | with canvas, 76 | 77 | - [demo](http://andrewxhill.github.io/cartodb-examples/videomap/inset/index.html) 78 | 79 | or with Vimeo, 80 | 81 | - [demo](http://andrewxhill.github.io/cartodb-examples/videomap/video-inset/index.html) 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /basic-maps/layer-selector/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Gmaps layer toggle | CartoDB.js 5 | 6 | 7 | 8 | 42 | 43 | 44 | 47 | 48 | 49 |
50 |
51 | 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 131 | 132 | -------------------------------------------------------------------------------- /double-map-alt/aww-rats.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Rats and Trees in NYC 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |

22 |
23 | 24 |

Map created by @andrewxhill

25 |
26 |

27 |
28 | 29 | 30 |
31 |

32 |
33 |

Rats and Trees in NYC

34 |

I have no idea what the relationship is between these two datasets, but I wanted to see them together :)

35 |
36 |

37 |
38 | 39 |
40 |
41 |
42 |
43 | 44 | 45 |
46 |

47 |
48 |

Description block

49 |
50 |

51 |
52 |
53 |

54 |
55 |

The left panel is all rat sightenings reported to 311 between 2009 and 2013. The right panel are all the trees in NYC.

56 |
57 |

58 |
59 | 60 | 61 | 62 | 63 | 64 | 69 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /double-map-alt/css/dark-theme.css: -------------------------------------------------------------------------------- 1 | /* Change the styles below in order to customize your template */ 2 | 3 | body{font-family: Helvetica, Arial; font-weight: regular; font-size: 15px; color: #CBCBCB; background-color: #333} 4 | h1{font-weight: bold; font-size: 31px; letter-spacing: -1px; color: #FFF; line-height: 33px; *margin-top:20px;} 5 | h3{font-weight: bold; font-size: 12px; color: #777; text-transform: uppercase; margin: 10px 0 0 0;} 6 | p{margin: 8px 0 20px 0; line-height: 18px;} 7 | a, a:visited{color: #72B6E5; text-decoration: none;} 8 | a:hover{text-decoration: underline;} 9 | 10 | .context{font-family: Helvetica, Arial; font-size: 13px; color: #999; padding: 10px 0 0 0;} 11 | .subheader{border-bottom: 1px solid #555;} 12 | .footer{border-top: 1px solid #555; margin-top: 20px;} 13 | .map{background-color:#eee; border-bottom: 1px solid #000; border-top: 1px solid #000; height: 440px; margin: 10px 0 25px 0;} 14 | .titleBlock{text-align: center;} 15 | -------------------------------------------------------------------------------- /double-map-alt/css/light-theme.css: -------------------------------------------------------------------------------- 1 | /* Change the styles below in order to customize your template */ 2 | 3 | body{font-family: Helvetica, Arial; font-weight: regular; font-size: 15px; color: #555; background-color: #FFF} 4 | h1{font-weight: bold; font-size: 31px; letter-spacing: -1px; color: #333; line-height: 33px; *margin-top:20px;} 5 | h3{font-weight: bold; font-size: 12px; color: #CCC; text-transform: uppercase; margin: 10px 0 0 0;} 6 | p{margin: 8px 0 20px 0; line-height: 18px;} 7 | a, a:visited{color: #397DB8; text-decoration: none;} 8 | a:hover{text-decoration: underline;} 9 | 10 | .context{font-family: Helvetica, Arial; font-size: 13px; color: #666; padding: 10px 0 0 0;} 11 | .subheader{border-bottom: 1px solid #ddd;} 12 | .footer{border-top: 1px solid #ddd; margin-top: 20px;} 13 | .map{background-color:#eee; border-bottom: 1px solid #ccc; border-top: 1px solid #ccc; height: 440px; margin: 10px 0 25px 0;} 14 | .titleBlock{text-align: center;} 15 | -------------------------------------------------------------------------------- /double-map-alt/css/makeitresponsive.css: -------------------------------------------------------------------------------- 1 | /* Here are the styles that makes the template responsive */ 2 | 3 | .row {width: 96%; max-width: 960px; margin: 0 auto; text-align: center;} 4 | .row:before, .row:after {content: " "; display: table;} 5 | .row:after {clear: both;} 6 | .row .row {width: 100%;} 7 | 8 | .col {display: inline; float: left; margin: 0 1%; position: relative;} 9 | .col .col {margin: 0 2%;} 10 | .col .col:first-child {margin-left: 0;} 11 | .col .col:last-child {margin-right: 0;} 12 | 13 | .span1 {width: 4.25%;} 14 | .span2 {width: 10.5%;} 15 | .span3 {width: 16.75%;} 16 | .span4 {width: 23%;} 17 | .span5 {width: 29.5%;} 18 | .span6 {width: 35.5%;} 19 | .span7 {width: 41.75%;} 20 | .span8 {width: 48%;} 21 | .span9 {width: 54.25%;} 22 | .span10 {width: 60.5%;} 23 | .span11 {width: 66.75%;} 24 | .span12 {width: 73%;} 25 | .span13 {width: 79.25%;} 26 | .span14 {width: 85.5%;} 27 | .span15 {width: 91.75%;} 28 | .span16 {width: 98%;} 29 | 30 | @media only screen and (max-width: 480px) { 31 | .col.span8 {margin: 0; width: 100%;} 32 | .col.span12 {margin: 0; width: 100%;} 33 | .col.span4 {margin: 0; text-align: left;} 34 | .col.footer {margin: 30px 0 0 0;} 35 | .col.span4.titleBlock h3 {margin: 5px 0 0 0;} 36 | .col.span2.empty {margin: 0; width: 100%; display: none} 37 | } 38 | -------------------------------------------------------------------------------- /intensity-time/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 39 | 40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /live-map/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Easy example | CartoDB.js 5 | 6 | 7 | 8 | 15 | 16 | 17 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /manhattanhenge/city/chicago.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sunset map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 106 | 107 | 108 |
109 | 110 | 111 | start animation 112 |
113 | 114 | 115 | 116 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /manhattanhenge/city/dc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sunset map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 106 | 107 | 108 |
109 | 110 | 111 | start animation 112 |
113 | 114 | 115 | 116 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /manhattanhenge/city/nyc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sunset map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 106 | 107 | 108 |
109 | 110 | 111 | start animation 112 |
113 | 114 | 115 | 116 | 117 | 120 | 121 | -------------------------------------------------------------------------------- /manhattanhenge/city/sf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sunset map 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 106 | 107 | 108 |
109 | 110 | 111 | start animation 112 |
113 | 114 | 115 | 116 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /manhattanhenge/img/bk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/bk.png -------------------------------------------------------------------------------- /manhattanhenge/img/chicago.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/chicago.png -------------------------------------------------------------------------------- /manhattanhenge/img/dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/dc.png -------------------------------------------------------------------------------- /manhattanhenge/img/nyc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/nyc.png -------------------------------------------------------------------------------- /manhattanhenge/img/nyc_districts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/nyc_districts.png -------------------------------------------------------------------------------- /manhattanhenge/img/pt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/pt3.png -------------------------------------------------------------------------------- /manhattanhenge/img/sf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/sf.png -------------------------------------------------------------------------------- /manhattanhenge/img/veknik_osm_london.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/manhattanhenge/img/veknik_osm_london.png -------------------------------------------------------------------------------- /manhattanhenge/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Gallery 7 | 8 | 9 | 10 | 11 | 68 | 69 | 70 |
71 |
72 | Cityhenge 73 | 74 | say hi @andrewxhill 75 | 76 |
77 |
78 |
79 |
80 | 81 |
82 | 83 | 84 | 85 |
86 | 87 | 88 |
89 | 90 | 91 | 92 |
93 | 94 | 95 |
96 | 97 | 98 | 99 |
100 | 101 | 102 |
103 | 104 | 105 | 106 |
107 | 108 |
109 |
110 | 111 | 112 | -------------------------------------------------------------------------------- /manhattanhenge/js/carto.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // Carto stylesheets support 3 | // 4 | // this is basically a hack on top of branch browser of carto 5 | // repository: Compiles carto to javascript shader 6 | //======================================== 7 | 8 | (function(VECNIK) { 9 | 10 | // monkey patch less classes 11 | tree.Value.prototype.toJS = function() { 12 | var v = this.value[0].value[0]; 13 | val = v.toString(); 14 | if(v.is === "color") { 15 | val = "'" + val + "'"; 16 | } 17 | return "_value = " + val + ";" 18 | } 19 | 20 | tree.Selector.prototype.toJS = function() { 21 | var self = this; 22 | var opMap = { 23 | '=': '===' 24 | }; 25 | var zoom = "(" + self.zoom + " & (1 << ctx.zoom))"; 26 | return [zoom].concat( 27 | _.map(this.filters, function(filter) { 28 | var op = filter.op; 29 | if(op in opMap) { 30 | op = opMap[op]; 31 | } 32 | var val = filter.val; 33 | if(filter._val !== undefined) { 34 | val = filter._val.toString(true); 35 | } 36 | 37 | var attrs = "data"; 38 | return attrs + "." + filter.key + " " + op + " " + val; 39 | }) 40 | ).join(" && "); 41 | } 42 | 43 | tree.Ruleset.prototype.toJS = function() { 44 | var shaderAttrs = {}; 45 | var _if = this.selectors[0].toJS(); 46 | _.each(this.rules, function(rule) { 47 | if(rule instanceof tree.Rule) { 48 | shaderAttrs[rule.name] = shaderAttrs[rule.name] || []; 49 | if (_if) { 50 | shaderAttrs[rule.name].push( 51 | "if(" + _if + "){" + rule.value.toJS() + "}" 52 | ); 53 | } else { 54 | shaderAttrs[rule.name].push(rule.value.toJS()); 55 | } 56 | } else { 57 | if (rule instanceof tree.Ruleset) { 58 | var sh = rule.toJS(); 59 | for(var v in sh) { 60 | shaderAttrs[v] = shaderAttrs[v] || []; 61 | for(var attr in sh[v]) { 62 | shaderAttrs[v].push(sh[v][attr]); 63 | } 64 | } 65 | } 66 | } 67 | }); 68 | return shaderAttrs; 69 | } 70 | 71 | function createFn(ops) { 72 | var body = ops.join('\n'); 73 | return Function("data","ctx", "var _value = null; " + body + "; return _value; "); 74 | } 75 | 76 | function toCartoShader(ruleset) { 77 | var shaderAttrs = {}; 78 | shaderAttrs = ruleset.rules[0].toJS(); 79 | try { 80 | for(var attr in shaderAttrs) { 81 | shaderAttrs[attr] = createFn(shaderAttrs[attr]); 82 | } 83 | } 84 | catch(e) { 85 | console.log("error creating shader"); 86 | console.log(e); 87 | return null; 88 | } 89 | 90 | 91 | return shaderAttrs; 92 | } 93 | 94 | /** 95 | * compile from Carto style to javascript shader 96 | */ 97 | var compile = function(style, callback) { 98 | 99 | var parse_env = { 100 | error: function(obj) { 101 | console.log("ERROR"); 102 | } 103 | }; 104 | 105 | var parser = new carto.Parser(parse_env); 106 | 107 | parser.parse(style, function(err, ruleset) { 108 | if(!err) { 109 | var shader = toCartoShader(ruleset); 110 | callback(shader); 111 | } else { 112 | callback(null); 113 | } 114 | }); 115 | } 116 | 117 | var init = function(callback) { 118 | carto_initialize(carto, './reference.json', function(carto) { 119 | VECNIK.Carto._carto = carto; 120 | if(callback) callback(carto); 121 | }); 122 | } 123 | 124 | VECNIK.Carto = { 125 | init: init, 126 | compile: compile 127 | }; 128 | 129 | })(VECNIK); 130 | 131 | if (typeof module !== 'undefined' && module.exports) { 132 | } 133 | -------------------------------------------------------------------------------- /manhattanhenge/js/cartodb.provider.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // CartoDB data provider 4 | //======================================== 5 | 6 | (function(VECNIK) { 7 | 8 | function CartoDBSQLAPI(opts) { 9 | this.projection = new VECNIK.MercatorProjection(); 10 | this.opts = opts; 11 | this.base_url = 'http://' + opts.user + ".cartodb.com/api/v2/sql"; 12 | 13 | //set defaults 14 | this.opts.ENABLE_SIMPLIFY = VECNIK.settings.get('ENABLE_SIMPLIFY'); 15 | this.opts.ENABLE_SNAPPING = VECNIK.settings.get('ENABLE_SNAPPING'); 16 | this.opts.ENABLE_CLIPPING = VECNIK.settings.get('ENABLE_CLIPPING'); 17 | this.opts.ENABLE_FIXING = VECNIK.settings.get('ENABLE_FIXING'); 18 | } 19 | 20 | CartoDBSQLAPI.prototype.debug = function(w) { 21 | if(this.opts.debug) { 22 | //console.log(w); 23 | } 24 | } 25 | 26 | CartoDBSQLAPI.prototype._sql_url = function(sql) { 27 | var self = this; 28 | this.debug(sql); 29 | return this.base_url + "?q=" + encodeURIComponent(sql) + "&format=geojson&dp=6"; 30 | } 31 | 32 | CartoDBSQLAPI.prototype.get_tile_data_sql = function(projection, table, x, y, zoom) { 33 | return VECNIK.CartoDB.SQL(projection, table, x, y, zoom, this.opts); 34 | }; 35 | 36 | CartoDBSQLAPI.prototype.url = function(coordinates) { 37 | var projection = this.projection; 38 | var opts = this.opts; 39 | var table = opts.table; 40 | var prj = this.projection; 41 | var sql = this.get_tile_data_sql(prj, table, coordinates.column, coordinates.row, coordinates.zoom); 42 | var sql_url = this._sql_url(sql); 43 | return sql_url; 44 | } 45 | 46 | VECNIK.CartoDB = VECNIK.CartoDB || {}; 47 | VECNIK.CartoDB.API = CartoDBSQLAPI; 48 | 49 | })(VECNIK); 50 | -------------------------------------------------------------------------------- /manhattanhenge/js/cartodb.sql.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // sql generator for cartodb 3 | //======================================== 4 | 5 | var VECNIK = VECNIK || {}; 6 | 7 | (function(VECNIK) { 8 | 9 | var sql = function(projection, table, x, y, zoom, opts) { 10 | 11 | opts = opts || { 12 | ENABLE_CLIPPING: false, 13 | ENABLE_SIMPLIFY: false, 14 | ENABLE_FIXING: false, 15 | ENABLE_SNAPPING: false 16 | }; 17 | var bbox = projection.tileBBox(x, y, zoom); 18 | var geom_column = '"the_geom"'; 19 | var geom_column_orig = '"the_geom"'; 20 | var id_column = 'cartodb_id'; 21 | var TILE_SIZE = 256; 22 | var tile_pixel_width = TILE_SIZE; 23 | var tile_pixel_height = TILE_SIZE; 24 | 25 | //console.log('-- ZOOM: ' + zoom); 26 | 27 | var tile_geo_width = bbox[1].lng() - bbox[0].lng(); 28 | var tile_geo_height = bbox[1].lat() - bbox[0].lat(); 29 | 30 | var pixel_geo_width = tile_geo_width / tile_pixel_width; 31 | var pixel_geo_height = tile_geo_height / tile_pixel_height; 32 | 33 | //console.log('-- PIXEL_GEO_SIZE: ' 34 | // + pixel_geo_width + ' x ' + pixel_geo_height); 35 | 36 | var pixel_geo_maxsize = Math.max(pixel_geo_width, pixel_geo_height); 37 | //console.log('-- MAX_SIZE: ' + pixel_geo_maxsize); 38 | 39 | var tolerance = pixel_geo_maxsize / 2; 40 | //console.log('-- TOLERANCE: ' + tolerance); 41 | 42 | // simplify 43 | var ENABLE_SIMPLIFY = opts.ENABLE_SIMPLIFY; 44 | if ( ENABLE_SIMPLIFY ) { 45 | geom_column = 'ST_Simplify(' + geom_column + ', ' + tolerance + ')'; 46 | // may change type 47 | geom_column = 'ST_CollectionExtract(' + geom_column + ', ST_Dimension( ' 48 | + geom_column_orig + ') + 1 )'; 49 | } 50 | 51 | // snap to a pixel grid 52 | var ENABLE_SNAPPING = opts.ENABLE_SNAPPING; 53 | if ( ENABLE_SNAPPING ) { 54 | geom_column = 'ST_SnapToGrid(' + geom_column + ', ' 55 | + pixel_geo_maxsize + ')'; 56 | // may change type 57 | geom_column = 'ST_CollectionExtract(' + geom_column + ', ST_Dimension( ' 58 | + geom_column_orig + ') + 1 )'; 59 | } 60 | 61 | // This is the query bounding box 62 | var sql_env = "ST_MakeEnvelope(" 63 | + bbox[0].lng() + "," + bbox[0].lat() + "," 64 | + bbox[1].lng() + "," + bbox[1].lat() + ", 4326)"; 65 | 66 | // clip 67 | var ENABLE_CLIPPING = opts.ENABLE_CLIPPING; 68 | if ( ENABLE_CLIPPING ) { 69 | 70 | // This is a slightly enlarged version of the query bounding box 71 | var sql_env_exp = 'ST_Expand(' + sql_env + ', ' 72 | + ( pixel_geo_maxsize * 2 ) + ')'; 73 | // Also must be snapped to the grid ... 74 | sql_env_exp = 'ST_SnapToGrid(' + sql_env_exp + ',' 75 | + pixel_geo_maxsize + ')'; 76 | 77 | // snap to box 78 | geom_column = 'ST_Snap(' + geom_column + ', ' + sql_env_exp 79 | + ', ' + pixel_geo_maxsize + ')'; 80 | 81 | // Make valid (both ST_Snap and ST_SnapToGrid and ST_Expand 82 | var ENABLE_FIXING = opts.ENABLE_FIXING; 83 | if ( ENABLE_FIXING ) { 84 | // NOTE: up to PostGIS-2.0.0 beta5 ST_MakeValid did not accept 85 | // points nor GeometryCollection objects 86 | geom_column = 'CASE WHEN ST_Dimension(' 87 | + geom_column + ') = 0 OR GeometryType(' 88 | + geom_column + ") = 'GEOMETRYCOLLECTION' THEN " 89 | + geom_column + ' ELSE ST_CollectionExtract(ST_MakeValid(' 90 | + geom_column + '), ST_Dimension(' + geom_column_orig 91 | + ') + 1 ) END'; 92 | } 93 | 94 | // clip by box 95 | geom_column = 'ST_Intersection(' + geom_column 96 | + ', ' + sql_env_exp + ')'; 97 | } 98 | 99 | var columns = id_column + ',' + geom_column + ' as the_geom'; 100 | if(opts.columns) { 101 | columns += ','; 102 | columns += opts.columns.join(',') 103 | columns += ' '; 104 | } 105 | 106 | // profiling only 107 | var COUNT_ONLY = opts.COUNT_ONLY || false; 108 | if ( COUNT_ONLY ) { 109 | columns = x + ' as x, ' + y + ' as y, sum(st_npoints(' 110 | + geom_column + ')) as the_geom'; 111 | } 112 | 113 | var sql = "select " + columns +" from " + table; 114 | sql += " WHERE the_geom && " + sql_env; 115 | //sql += " LIMIT 100"; 116 | 117 | //console.log('-- SQL: ' + sql); 118 | 119 | return sql; 120 | }; 121 | 122 | VECNIK.CartoDB = VECNIK.CartoDB || {}; 123 | VECNIK.CartoDB.SQL = sql; 124 | 125 | })(VECNIK); 126 | 127 | if (typeof module !== 'undefined' && module.exports) { 128 | module.exports.CartoDBSQL = VECNIK.CartoDB.SQL; 129 | } 130 | 131 | -------------------------------------------------------------------------------- /manhattanhenge/js/core.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // Core 3 | // 4 | // base classes 5 | //======================================== 6 | 7 | // create root scope if not exists 8 | var VECNIK = VECNIK || {}; 9 | 10 | (function(VECNIK) { 11 | 12 | //======================================== 13 | // Events 14 | // 15 | // event management 16 | //======================================== 17 | 18 | function Event() {} 19 | Event.prototype.on = function(evt, callback) { 20 | var cb = this.callbacks = this.callbacks || {}; 21 | var l = cb[evt] || (cb[evt] = []); 22 | l.push(callback); 23 | }; 24 | 25 | Event.prototype.emit = function(evt) { 26 | var c = this.callbacks && this.callbacks[evt]; 27 | for(var i = 0; c && i < c.length; ++i) { 28 | c[i].apply(this, Array.prototype.slice.call(arguments, 1)); 29 | } 30 | }; 31 | 32 | 33 | // http get 34 | // should be improved 35 | function get(url, callback) { 36 | var mygetrequest= new XMLHttpRequest(); 37 | mygetrequest.onreadystatechange=function() { 38 | if (mygetrequest.readyState == 4){ 39 | if (mygetrequest.status == 200){ 40 | callback(JSON.parse(mygetrequest.responseText)); 41 | } 42 | else { 43 | //error 44 | } 45 | } 46 | }; 47 | mygetrequest.open("GET", url, true) 48 | mygetrequest.send(null) 49 | } 50 | 51 | //======================================== 52 | // model 53 | // 54 | // pretty basic model funcionallity 55 | //======================================== 56 | 57 | function Model() { 58 | //this.data = {}; // serializable data 59 | } 60 | 61 | Model.prototype = new Event(); 62 | 63 | Model.prototype.set = function(data, silent) { 64 | this.data = this.data || {}; 65 | for(var v in data) { 66 | if(data.hasOwnProperty(v)) { 67 | this.data[v] = data[v]; 68 | } 69 | } 70 | if(!silent) { 71 | this.emit('change', this.data); 72 | } 73 | }; 74 | 75 | Model.prototype.get = function(attr, def) { 76 | if(this.data) { 77 | if(attr in this.data) { 78 | return this.data[attr]; 79 | } 80 | return def; 81 | } 82 | return def; 83 | }; 84 | 85 | /** 86 | * delete the attribute 87 | */ 88 | Model.prototype.unset = function(attr, silent) { 89 | delete this.data[attr]; 90 | if(!silent) { 91 | this.emit('change', this.data); 92 | } 93 | }; 94 | 95 | Model.prototype.destroy = function() { 96 | this.emit('destroy'); 97 | delete this.data; 98 | }; 99 | 100 | 101 | VECNIK.Event = Event; 102 | VECNIK.Model = Model; 103 | VECNIK.get = get; 104 | 105 | })(VECNIK); 106 | 107 | if (typeof module !== 'undefined' && module.exports) { 108 | module.exports.Event = VECNIK.Event; 109 | module.exports.Model = VECNIK.Model; 110 | } 111 | -------------------------------------------------------------------------------- /manhattanhenge/js/geojson.provider.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // CartoDB data provider 4 | //======================================== 5 | 6 | (function(VECNIK) { 7 | 8 | function GeoJSONTile(opts) { 9 | this.opts = opts; 10 | this.template = opts.template; 11 | } 12 | 13 | GeoJSONTile.prototype.url = function(coord) { 14 | return this.template 15 | .replace('{z}', coord.zoom.toFixed(0)) 16 | .replace('{x}', coord.column.toFixed(0)) 17 | .replace('{y}', coord.row.toFixed(0)); 18 | } 19 | 20 | VECNIK.GeoJSONTile = GeoJSONTile; 21 | 22 | })(VECNIK); 23 | -------------------------------------------------------------------------------- /manhattanhenge/js/geometry.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // geometry conversion 4 | //======================================== 5 | 6 | var VECNIK = VECNIK || {}; 7 | 8 | (function(VECNIK) { 9 | 10 | var LatLng = VECNIK.LatLng; 11 | var Point = VECNIK.Point; 12 | 13 | //stats 14 | var stats = { 15 | vertices: 0 16 | }; 17 | 18 | var latlng = new LatLng(0, 0); 19 | var prj = new VECNIK.MercatorProjection(); 20 | 21 | function map_latlon(ll, x, y, zoom) { 22 | latlng.latitude = ll[1]; 23 | latlng.longitude = ll[0]; 24 | stats.vertices++; 25 | var point = prj.latLngToTilePoint(latlng, x, y, zoom); 26 | //point.x = point.x >> 0; 27 | //point.y = point.y >> 0; 28 | return point; 29 | } 30 | 31 | var primitive_conversion = { 32 | 'LineString': function(x, y, zoom, coordinates) { 33 | var converted = []; 34 | var pc = primitive_conversion['Point']; 35 | for(var i=0; i < coordinates.length; ++i) { 36 | converted.push(pc(x, y, zoom, coordinates[i])); 37 | } 38 | return converted; 39 | }, 40 | 41 | 'Point': function(x, y, zoom, coordinates) { 42 | return map_latlon(coordinates, x, y, zoom); 43 | }, 44 | 45 | 'MultiPoint': function(x, y, zoom, coordinates) { 46 | var converted = []; 47 | var pc = primitive_conversion['Point']; 48 | for(var i=0; i < coordinates.length; ++i) { 49 | converted.push(pc(x, y, zoom, coordinates[i])); 50 | } 51 | return converted; 52 | }, 53 | //do not manage inner polygons! 54 | 'Polygon': function(x, y, zoom, coordinates) { 55 | if(coordinates[0]) { 56 | var coords = []; 57 | for(var i=0; i < coordinates[0].length; ++i) { 58 | coords.push(map_latlon(coordinates[0][i], x, y, zoom)); 59 | } 60 | return [coords]; 61 | } 62 | return null; 63 | }, 64 | 'MultiPolygon': function(x, y, zoom, coordinates) { 65 | var polys = []; 66 | var poly; 67 | var pc = primitive_conversion['Polygon']; 68 | for(var i=0; i < coordinates.length; ++i) { 69 | poly = pc(x, y, zoom, coordinates[i]); 70 | if(poly) 71 | polys.push(poly); 72 | } 73 | return polys; 74 | } 75 | }; 76 | 77 | var project_geometry = function(geometry, zoom, x, y) { 78 | var conversor = primitive_conversion[geometry.type]; 79 | if(conversor) { 80 | return conversor(x, y , zoom, geometry.coordinates); 81 | } 82 | }; 83 | 84 | VECNIK.project_geometry = project_geometry; 85 | VECNIK.geometry_stats = stats; 86 | 87 | })(VECNIK); 88 | 89 | if (typeof module !== 'undefined' && module.exports) { 90 | module.exports.project_geometry = VECNIK.project_geometry; 91 | } 92 | if (typeof self !== 'undefined') { 93 | self.VECNIK = VECNIK; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /manhattanhenge/js/mercator.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // Mercator projection 4 | //======================================== 5 | // 6 | var VECNIK = VECNIK || {}; 7 | 8 | (function(VECNIK) { 9 | 10 | var TILE_SIZE = 256; 11 | 12 | // todo: move outside 13 | function Point(x, y) { 14 | this.x = x || 0; 15 | this.y = y || 0; 16 | } 17 | 18 | function LatLng(lat, lon) { 19 | this.latitude = lat || 0; 20 | this.longitude = lon || 0; 21 | } 22 | 23 | LatLng.prototype.lat = function() { 24 | return this.latitude; 25 | } 26 | 27 | LatLng.prototype.lng = function() { 28 | return this.longitude; 29 | } 30 | 31 | function bound(value, opt_min, opt_max) { 32 | if (opt_min != null) value = Math.max(value, opt_min); 33 | if (opt_max != null) value = Math.min(value, opt_max); 34 | return value; 35 | } 36 | 37 | function degreesToRadians(deg) { 38 | return deg * (Math.PI / 180); 39 | } 40 | 41 | function radiansToDegrees(rad) { 42 | return rad / (Math.PI / 180); 43 | } 44 | 45 | function MercatorProjection() { 46 | this.pixelOrigin_ = new Point(TILE_SIZE / 2, TILE_SIZE / 2); 47 | this.pixelsPerLonDegree_ = TILE_SIZE / 360; 48 | this.pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI); 49 | } 50 | 51 | MercatorProjection.prototype.fromLatLngToPoint = function (latLng, opt_point) { 52 | var me = this; 53 | var point = opt_point || new Point(0, 0); 54 | var origin = me.pixelOrigin_; 55 | 56 | point.x = origin.x + latLng.lng() * me.pixelsPerLonDegree_; 57 | 58 | // NOTE(appleton): Truncating to 0.9999 effectively limits latitude to 59 | // 89.189. This is about a third of a tile past the edge of the world 60 | // tile. 61 | var siny = bound(Math.sin(degreesToRadians(latLng.lat())), -0.9999, 62 | 0.9999); 63 | point.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny)) * 64 | -me.pixelsPerLonRadian_; 65 | return point; 66 | }; 67 | 68 | MercatorProjection.prototype.fromPointToLatLng = function (point) { 69 | var me = this; 70 | var origin = me.pixelOrigin_; 71 | var lng = (point.x - origin.x) / me.pixelsPerLonDegree_; 72 | var latRadians = (point.y - origin.y) / -me.pixelsPerLonRadian_; 73 | var lat = radiansToDegrees(2 * Math.atan(Math.exp(latRadians)) - 74 | Math.PI / 2); 75 | return new LatLng(lat, lng); 76 | }; 77 | 78 | MercatorProjection.prototype.tileBBox = function(x, y, zoom) { 79 | var numTiles = 1 << zoom; 80 | var inc = TILE_SIZE/numTiles; 81 | var px = x*TILE_SIZE/numTiles; 82 | var py = y*TILE_SIZE/numTiles; 83 | return [ 84 | this.fromPointToLatLng(new Point(px, py + inc)), 85 | this.fromPointToLatLng(new Point(px + inc, py)) 86 | ]; 87 | }; 88 | 89 | MercatorProjection.prototype.tilePoint = function(x, y, zoom) { 90 | var numTiles = 1 << zoom; 91 | var px = x*TILE_SIZE; 92 | var py = y*TILE_SIZE; 93 | return [px, py]; 94 | }; 95 | 96 | MercatorProjection.prototype.latLngToTilePoint = function(latLng, x, y, zoom) { 97 | var numTiles = 1 << zoom; 98 | var projection = this; 99 | var worldCoordinate = projection.fromLatLngToPoint(latLng); 100 | var pixelCoordinate = new Point( 101 | worldCoordinate.x * numTiles, 102 | worldCoordinate.y * numTiles); 103 | var tp = this.tilePoint(x, y, zoom); 104 | return new Point( 105 | Math.floor(pixelCoordinate.x - tp[0]), 106 | Math.floor(pixelCoordinate.y - tp[1])); 107 | }; 108 | 109 | MercatorProjection.prototype.latLngToTile = function(latLng, zoom) { 110 | var numTiles = 1 << zoom; 111 | var projection = this; 112 | var worldCoordinate = projection.fromLatLngToPoint(latLng); 113 | var pixelCoordinate = new Point( 114 | worldCoordinate.x * numTiles, 115 | worldCoordinate.y * numTiles); 116 | return new Point( 117 | Math.floor(pixelCoordinate.x / TILE_SIZE), 118 | Math.floor(pixelCoordinate.y / TILE_SIZE)); 119 | }; 120 | 121 | VECNIK.LatLng = LatLng; 122 | VECNIK.Point = Point; 123 | VECNIK.MercatorProjection = MercatorProjection; 124 | 125 | })(VECNIK); 126 | 127 | if (typeof module !== 'undefined' && module.exports) { 128 | module.exports.MercatorProjection = VECNIK.MercatorProjection; 129 | module.exports.LatLng = VECNIK.LatLng; 130 | module.exports.Point = VECNIK.Point; 131 | } 132 | 133 | if (typeof self !== 'undefined') { 134 | self.VECNIK = VECNIK; 135 | } 136 | -------------------------------------------------------------------------------- /manhattanhenge/js/model.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // vecnik models 3 | //======================================== 4 | 5 | (function(VECNIK) { 6 | 7 | // utility 8 | function Profiler(name){ 9 | this.t0 = 0; 10 | this.unit = ''; 11 | } 12 | Profiler.prototype.start = function(unit) { 13 | this.t0 = new Date().getTime(); 14 | this.unit = unit || ''; 15 | } 16 | Profiler.prototype.end= function() { 17 | var t = new Date().getTime() - this.t0; 18 | //console.log("PROFILE - " + this.unit + ":" + t); 19 | return t; 20 | } 21 | 22 | //======================================== 23 | // tile model 24 | //======================================== 25 | function Tile(x, y, zoom) { 26 | this.x = x; 27 | this.y = y; 28 | this.zoom = zoom; 29 | this.on('change', this.precache.bind(this)) 30 | this.stats = { 31 | conversion_time: 0, 32 | vertices: 0, 33 | primitive_count: 0 34 | }; 35 | this.profiler = new Profiler('tile'); 36 | } 37 | 38 | Tile.prototype = new VECNIK.Model(); 39 | 40 | Tile.prototype.key = function() { 41 | return [this.x, this.y, this.zoom].join('-'); 42 | } 43 | 44 | Tile.prototype.geometry = function() { 45 | return this.get('geometry'); 46 | } 47 | 48 | Tile.prototype.precache = function() { 49 | var self = this; 50 | var geometry = []; 51 | this.profiler.start('conversion_time'); 52 | var primitives = this.data.features; 53 | var vertex_count = VECNIK.geometry_stats.vertices; 54 | if(VECNIK.settings.get('WEBWORKERS') && typeof Worker !== undefined) { 55 | var worker = new Worker('../js/projector.worker.js'); 56 | worker.onmessage = function(ev) { 57 | self.set({geometry: ev.data.geometry}, true); 58 | self.unset('features', true); 59 | self.emit('geometry_ready'); 60 | }; 61 | worker.postMessage({ 62 | primitives: primitives, 63 | zoom: this.zoom, 64 | x: this.x, 65 | y: this.y 66 | }); 67 | 68 | } else { 69 | for(var i = 0; i < primitives.length; ++i) { 70 | var p = primitives[i]; 71 | if(p.geometry) { 72 | var converted = VECNIK.project_geometry(p.geometry, this.zoom, this.x, this.y); 73 | if(converted && converted.length !== 0) { 74 | geometry.push({ 75 | vertexBuffer: converted, 76 | type: p.geometry.type, 77 | metadata: p.properties 78 | }); 79 | } else { 80 | delete p.geometry.coordinates; 81 | } 82 | } 83 | } 84 | this.set({geometry: geometry}, true); 85 | this.unset('features', true); 86 | this.emit('geometry_ready'); 87 | } 88 | this.stats.vertices = VECNIK.geometry_stats.vertices - vertex_count; 89 | this.stats.primitive_count = primitives.length; 90 | this.stats.conversion_time = this.profiler.end(); 91 | } 92 | 93 | 94 | //======================================== 95 | // tile manager 96 | //======================================== 97 | 98 | function TileManager(dataProvider) { 99 | this.tiles = {}; 100 | this.dataProvider = dataProvider; 101 | } 102 | 103 | TileManager.prototype.tileIndex= function(coordinates) { 104 | return coordinates.toKey(); 105 | } 106 | 107 | TileManager.prototype.get = function(coordinates) { 108 | return this.tiles[this.tileIndex(coordinates)]; 109 | } 110 | 111 | TileManager.prototype.destroy= function(coordinates) { 112 | var tile = this.tiles[this.tileIndex(coordinates)]; 113 | if(tile) { 114 | tile.destroy(); 115 | //console.log("removing " + this.tileIndex(coordinates)); 116 | delete this.tiles[this.tileIndex(coordinates)]; 117 | } 118 | } 119 | 120 | TileManager.prototype.add = function(coordinates) { 121 | //console.log("adding" + this.tileIndex(coordinates)); 122 | var tile = this.tiles[this.tileIndex(coordinates)] = new Tile( 123 | coordinates.column, 124 | coordinates.row, 125 | coordinates.zoom 126 | ); 127 | 128 | VECNIK.get(this.dataProvider.url(coordinates), function(data) { 129 | tile.set(data); 130 | }); 131 | return tile; 132 | } 133 | 134 | VECNIK.Tile = Tile; 135 | VECNIK.TileManager = TileManager; 136 | VECNIK.Profiler = Profiler; 137 | 138 | })(VECNIK); 139 | 140 | if (typeof module !== 'undefined' && module.exports) { 141 | module.exports.Tile = VECNIK.Tile; 142 | module.exports.TileManager = VECNIK.TileManager; 143 | } 144 | 145 | 146 | -------------------------------------------------------------------------------- /manhattanhenge/js/projector.worker.js: -------------------------------------------------------------------------------- 1 | importScripts('../js/mercator.js'); 2 | importScripts('../js/geometry.js'); 3 | 4 | self.onmessage = function(event) { 5 | var data = event.data; 6 | var primitives = data.primitives; 7 | var geometry = []; 8 | for(var i = 0; i < primitives.length; ++i) { 9 | var p = primitives[i]; 10 | if(p.geometry) { 11 | var converted = VECNIK.project_geometry(p.geometry, 12 | data.zoom, data.x, data.y); 13 | if(converted && converted.length !== 0) { 14 | geometry.push({ 15 | vertexBuffer: converted, 16 | type: p.geometry.type, 17 | metadata: p.properties 18 | }); 19 | } 20 | } 21 | } 22 | self.postMessage({geometry: geometry}); 23 | }; 24 | -------------------------------------------------------------------------------- /manhattanhenge/js/renderer.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // vecnik views 3 | //======================================== 4 | 5 | (function(VECNIK) { 6 | 7 | function Renderer() { 8 | var self = this; 9 | var primitive_render = this.primitive_render = { 10 | 'Point': function(ctx, coordinates) { 11 | ctx.save(); 12 | var radius = 2; 13 | var p = coordinates; 14 | ctx.translate(p.x, p.y); 15 | ctx.beginPath(); 16 | ctx.arc(radius, radius, radius, 0, Math.PI * 2, true); 17 | ctx.closePath(); 18 | ctx.fill(); 19 | ctx.stroke(); 20 | ctx.restore(); 21 | }, 22 | 'MultiPoint': function(ctx, coordinates) { 23 | var prender = primitive_render['Point']; 24 | for(var i=0; i < coordinates.length; ++i) { 25 | prender(ctx, coordinates[i]); 26 | } 27 | }, 28 | 'Polygon': function(ctx, coordinates) { 29 | ctx.beginPath(); 30 | var p = coordinates[0][0]; 31 | ctx.moveTo(p.x, p.y); 32 | for(var i=0; i < coordinates[0].length; ++i) { 33 | p = coordinates[0][i]; 34 | ctx.lineTo(p.x, p.y); 35 | } 36 | ctx.closePath(); 37 | ctx.fill(); 38 | //ctx.stroke(); 39 | }, 40 | 'MultiPolygon': function(ctx, coordinates) { 41 | var prender = primitive_render['Polygon']; 42 | for(var i=0; i < coordinates.length; ++i) { 43 | prender(ctx, coordinates[i]); 44 | } 45 | }, 46 | 'LineString': function(ctx, coordinates) { 47 | ctx.beginPath(); 48 | var p = coordinates[0]; 49 | ctx.moveTo(p.x, p.y); 50 | for(var i=0; i < coordinates.length; ++i) { 51 | p = coordinates[i]; 52 | ctx.lineTo(p.x, p.y); 53 | } 54 | ctx.stroke(); 55 | } 56 | }; 57 | } 58 | 59 | Renderer.prototype.render = function(ctx, geometry, zoom, shader) { 60 | var primitive_render = this.primitive_render; 61 | ctx.canvas.width = ctx.canvas.width; 62 | var primitive_type; 63 | if(geometry && geometry.length) { 64 | for(var i = 0; i < geometry.length; ++i) { 65 | var geo = geometry[i]; 66 | var primitive_type = geo.type; 67 | var renderer = primitive_render[primitive_type]; 68 | if(renderer) { 69 | // render visible tile 70 | var render_context = { 71 | zoom: zoom, 72 | id: i 73 | }; 74 | var is_active = true; 75 | if(shader) { 76 | is_active = shader.needs_render(geo.metadata, render_context, primitive_type); 77 | if(is_active) { 78 | shader.reset(ctx, primitive_type); 79 | shader.apply(ctx, geo.metadata, render_context); 80 | } 81 | } 82 | if (is_active) { 83 | renderer(ctx, geo.vertexBuffer); 84 | } 85 | } 86 | } 87 | } 88 | }; 89 | 90 | //======================================== 91 | // Canvas tile view 92 | //======================================== 93 | function CanvasTileView(tile, shader, renderer) { 94 | this.tileSize = new VECNIK.Point(256, 256); 95 | var canvas = document.createElement('canvas'); 96 | canvas.width = this.tileSize.x; 97 | canvas.height = this.tileSize.y; 98 | this.ctx = canvas.getContext('2d'); 99 | this.canvas = canvas; 100 | 101 | var backCanvas = document.createElement('canvas'); 102 | backCanvas.width = this.tileSize.x; 103 | backCanvas.height = this.tileSize.y; 104 | this.backCtx = backCanvas.getContext('2d'); 105 | this.backCanvas = backCanvas; 106 | 107 | this.el = canvas; 108 | this.id = tile.key(); 109 | this.el.setAttribute('id', tile.key()); 110 | var self = this; 111 | this.tile = tile; 112 | var render = function(){self.render();}; 113 | tile.on('geometry_ready', render); 114 | 115 | // shader 116 | this.shader = shader; 117 | if(shader) { 118 | shader.on('change', render); 119 | } 120 | this.renderer = renderer || new Renderer(); 121 | 122 | this.profiler = new VECNIK.Profiler('tile_render'); 123 | this.stats = { 124 | rendering_time: 0 125 | } 126 | } 127 | 128 | CanvasTileView.prototype.remove = function() { 129 | } 130 | 131 | CanvasTileView.prototype.render = function() { 132 | var ctx = this.ctx; 133 | 134 | this.profiler.start('render'); 135 | var BACKBUFFER = true; 136 | if(BACKBUFFER) { 137 | this.backCanvas.width = this.backCanvas.width; 138 | this.renderer.render(this.backCtx, this.tile.geometry(), this.tile.zoom, this.shader); 139 | this.canvas.width = this.canvas.width; 140 | this.ctx.drawImage(this.backCanvas, 0, 0); 141 | } else { 142 | this.renderer.render(ctx, this.tile.geometry(), this.tile.zoom, this.shader); 143 | } 144 | 145 | this.stats.rendering_time = this.profiler.end(); 146 | } 147 | 148 | 149 | //======================================== 150 | // Map view 151 | // manages the list of tiles 152 | //======================================== 153 | function CanvasMapView() { 154 | this.tile_views = {}; 155 | } 156 | 157 | CanvasMapView.prototype.add = function(canvasview) { 158 | this.tile_views[canvasview.id] = canvasview; 159 | } 160 | 161 | CanvasMapView.prototype.getByElement = function(el) { 162 | return this.tile_views[el.getAttribute('id')]; 163 | } 164 | 165 | 166 | VECNIK.Renderer = Renderer; 167 | VECNIK.CanvasTileView = CanvasTileView; 168 | VECNIK.CanvasMapView = CanvasMapView; 169 | 170 | })(VECNIK); 171 | 172 | if (typeof module !== 'undefined' && module.exports) { 173 | } 174 | 175 | 176 | -------------------------------------------------------------------------------- /manhattanhenge/js/settings.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // Global settings 4 | //======================================== 5 | 6 | var VECNIK = VECNIK || {}; 7 | 8 | (function(VECNIK) { 9 | 10 | function Settings(defaults) { 11 | this.set(defaults); 12 | } 13 | 14 | Settings.prototype = new VECNIK.Model(); 15 | 16 | // default settings 17 | VECNIK.settings = new Settings({ 18 | WEBWORKERS: false, 19 | BACKBUFFER: true, 20 | ENABLE_SIMPLIFY: true, 21 | ENABLE_SNAPPING: true, 22 | ENABLE_CLIPPING: true, 23 | ENABLE_FIXING: true 24 | }); 25 | 26 | })(VECNIK); 27 | 28 | if (typeof module !== 'undefined' && module.exports) { 29 | module.exports.settings = VECNIK.settings; 30 | } 31 | if (typeof self !== 'undefined') { 32 | self.VECNIK = VECNIK; 33 | } 34 | -------------------------------------------------------------------------------- /manhattanhenge/js/shader.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // shader 4 | //======================================== 5 | 6 | (function(VECNIK) { 7 | 8 | var mapper = { 9 | 'point-color': 'fillStyle', 10 | 'line-color': 'strokeStyle', 11 | 'line-width': 'lineWidth', 12 | 'line-opacity': 'globalAlpha', 13 | 'polygon-fill': 'fillStyle', 14 | 'polygon-opacity': 'globalAlpha' 15 | }; 16 | 17 | function CartoShader(shader) { 18 | this.compiled = {}; 19 | this.shader_src = null; 20 | this.compile(shader) 21 | } 22 | 23 | CartoShader.prototype = new VECNIK.Event(); 24 | 25 | CartoShader.prototype.compile = function(shader) { 26 | if(typeof shader === 'string') { 27 | shader = eval("(function() { return " + shader +"; })()"); 28 | } 29 | this.shader_src = shader; 30 | for(var attr in shader) { 31 | var c = mapper[attr]; 32 | if(c) { 33 | this.compiled[c] = eval("(function() { return shader[attr]; })();"); 34 | } 35 | } 36 | 37 | this.emit('change'); 38 | }; 39 | 40 | var needed_settings = { 41 | 'LineString': [ 42 | 'line-color', 43 | 'line-width', 44 | 'line-opacity' 45 | ], 46 | 'Polygon': [ 47 | 'polygon-fill' 48 | ], 49 | 'MultiPolygon': [ 50 | 'polygon-fill' 51 | ] 52 | }; 53 | var defaults = { 54 | 'LineString': { 55 | 'strokeStyle': '#000', 56 | 'lineWidth': 1, 57 | 'globalAlpha': 1.0, 58 | 'lineCap': 'round' 59 | }, 60 | 'Polygon': { 61 | 'strokeStyle': '#000', 62 | 'lineWidth': 1, 63 | 'globalAlpha': 1.0 64 | }, 65 | 'MultiPolygon': { 66 | 'strokeStyle': '#000', 67 | 'lineWidth': 1, 68 | 'globalAlpha': 1.0 69 | } 70 | }; 71 | 72 | CartoShader.prototype.needs_render = function(data, render_context, primitive_type) { 73 | var variables = needed_settings[primitive_type]; 74 | var shader = this.compiled; 75 | for(var attr in variables) { 76 | var style_attr = variables[attr]; 77 | var attr_present = this.shader_src[style_attr]; 78 | if(attr_present !== undefined) { 79 | var fn = shader[mapper[style_attr]]; 80 | if(typeof fn === 'function') { 81 | fn = fn(data, render_context); 82 | } 83 | if(fn !== null && fn !== undefined) { 84 | return true; 85 | } 86 | } 87 | } 88 | return false; 89 | 90 | } 91 | 92 | CartoShader.prototype.reset = function(ctx, primitive_type) { 93 | var def = defaults[primitive_type]; 94 | for(var attr in def) { 95 | ctx[attr] = def[attr]; 96 | } 97 | } 98 | 99 | CartoShader.prototype.apply = function(canvas_ctx, data, render_context) { 100 | var shader = this.compiled; 101 | for(var attr in shader) { 102 | var fn = shader[attr]; 103 | if(typeof fn === 'function') { 104 | fn = fn(data, render_context); 105 | } 106 | if(fn !== null && canvas_ctx[attr] != fn) { 107 | canvas_ctx[attr] = fn; 108 | } 109 | } 110 | }; 111 | 112 | VECNIK.CartoShader = CartoShader; 113 | 114 | })(VECNIK); 115 | 116 | if (typeof module !== 'undefined' && module.exports) { 117 | module.exports.CartoShader = CartoShader; 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /manhattanhenge/js/vecnik.leatlet.js: -------------------------------------------------------------------------------- 1 | L.TileLayer.Canvas = L.TileLayer.extend({ 2 | options: { 3 | async: false 4 | }, 5 | 6 | initialize: function (options) { 7 | this.tileSize = tileSize || new MM.Point(256, 256) 8 | this.tiles = new CartoDBSQLAPI({ 9 | user: 'vizzuality', 10 | table: 'countries_final', 11 | columns: ['admin'], 12 | }); 13 | this.views = new CanvasMapView(); 14 | }, 15 | 16 | redraw: function () { 17 | }, 18 | 19 | 20 | _createTileProto: function () { 21 | var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile'); 22 | 23 | var tileSize = this.options.tileSize; 24 | proto.width = tileSize; 25 | proto.height = tileSize; 26 | }, 27 | 28 | _createTile: function () { 29 | var tile = this._canvasProto.cloneNode(false); 30 | tile.onselectstart = tile.onmousemove = L.Util.falseFn; 31 | return tile; 32 | }, 33 | 34 | _loadTile: function (tile, tilePoint, zoom) { 35 | tile._layer = this; 36 | tile._tilePoint = tilePoint; 37 | tile._zoom = zoom; 38 | 39 | this.drawTile(tile, tilePoint, zoom); 40 | 41 | if (!this.options.async) { 42 | this.tileDrawn(tile); 43 | } 44 | }, 45 | _resetTile: function (tile) { 46 | }, 47 | 48 | drawTile: function (tile, tilePoint, zoom) { 49 | // override with rendering code 50 | }, 51 | 52 | tileDrawn: function (tile) { 53 | this._tileOnLoad.call(tile); 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /manhattanhenge/js/vecnik.modestmaps.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // Core 3 | // 4 | // base classes 5 | //======================================== 6 | 7 | // create root scope if not exists 8 | var VECNIK = VECNIK || {}; 9 | 10 | (function(VECNIK) { 11 | var MM = com.modestmaps; 12 | 13 | 14 | //======================================== 15 | // testing provider with mapbox tile layer 16 | //======================================== 17 | function TileManagerMapBox() { 18 | } 19 | TileManagerMapBox.prototype = new VECNIK.TileManager(); 20 | TileManagerMapBox.prototype.url = function(coordinates) { 21 | return 'http://b.tiles.mapbox.com/v3/mapbox.mapbox-streets/' + coordinates.zoom + '/' + coordinates.row + '/' + coordinates.column + ".png"; 22 | } 23 | 24 | 25 | //======================================== 26 | // Canvas provider 27 | //======================================== 28 | function CanvasProvider(dataSource, shader, renderer, tileSize) { 29 | this.tileSize = tileSize || new MM.Point(256, 256) 30 | this.renderer = renderer; 31 | this.tiles = new VECNIK.TileManager(dataSource) 32 | this.views = new VECNIK.CanvasMapView(shader); 33 | this.shader = shader; 34 | } 35 | 36 | CanvasProvider.prototype.getTile = function(coord) { 37 | var tile = this.tiles.add(coord); 38 | var canvas = new VECNIK.CanvasTileView(tile, this.shader, this.renderer); 39 | this.views.add(canvas); 40 | return canvas.el; 41 | } 42 | 43 | CanvasProvider.prototype.releaseTile = function(coordinates) { 44 | this.tiles.destroy(coordinates); 45 | }; 46 | 47 | MM.extend(CanvasProvider, MM.MapProvider); 48 | 49 | VECNIK.MM = { 50 | CanvasProvider: CanvasProvider, 51 | TileManagerMapBox: TileManagerMapBox 52 | }; 53 | 54 | })(VECNIK); 55 | 56 | -------------------------------------------------------------------------------- /manhattanhenge/src/core.js: -------------------------------------------------------------------------------- 1 | 2 | vecnik = window.vecnik || {}; 3 | 4 | window.requestAnimFrame = (function(){ 5 | return window.requestAnimationFrame || 6 | window.webkitRequestAnimationFrame || 7 | window.mozRequestAnimationFrame || 8 | window.oRequestAnimationFrame || 9 | window.msRequestAnimationFrame || 10 | function( callback ){ 11 | window.setTimeout(callback, 1000 / 60); 12 | }; 13 | })(); 14 | 15 | var extend = function(obj, prop) { 16 | for(var p in prop) { 17 | obj[p] = prop[p]; 18 | } 19 | } 20 | 21 | vecnik.extend = extend; 22 | 23 | function Event() {} 24 | Event.prototype.on = function(evt, callback) { 25 | var cb = this.callbacks = this.callbacks || {}; 26 | var l = cb[evt] || (cb[evt] = []); 27 | l.push(callback); 28 | }; 29 | 30 | Event.prototype.emit = function(evt) { 31 | var c = this.callbacks && this.callbacks[evt]; 32 | for(var i = 0; c && i < c.length; ++i) { 33 | c[i].apply(this, Array.prototype.slice.call(arguments, 1)); 34 | } 35 | }; 36 | 37 | vecnik.Events = Event; 38 | 39 | // simple tree implementation 40 | // usage: 41 | // extend(obj, vecnik.Tree) 42 | vecnik.Tree = { 43 | 44 | add: function(node) { 45 | if(!node) return this; 46 | var cb = this.children(); 47 | node._parent = this; 48 | cb.push(node); 49 | return this; 50 | }, 51 | 52 | remove: function(node) { 53 | var cb = this.children(); 54 | if(cb) { 55 | for(var c in cb) { 56 | if(node == cb[c]) { 57 | node._parent = null; 58 | this._children.splice(c, 1); 59 | return; 60 | } 61 | } 62 | } 63 | return this; 64 | }, 65 | 66 | children: function() { 67 | return this._children || (this._children = []); 68 | }, 69 | 70 | each: function(fn) { 71 | this.children().forEach(fn); 72 | return this; 73 | }, 74 | 75 | any: function(fn) { 76 | for(var i in this.children()) { 77 | if(fn(this._children[i])) 78 | return true; 79 | } 80 | return false; 81 | } 82 | 83 | } 84 | 85 | vecnik.timer = (function() { 86 | 87 | function timer(fn) { 88 | if(fn._timer) return; 89 | fn._last = new Date().getTime(); 90 | fn._timer = timer; 91 | timer.add(fn); 92 | requestAnimFrame(timer.tick); 93 | } 94 | 95 | timer.tick = function() { 96 | var remove = []; 97 | timer.each(function(n) { 98 | var now = new Date().getTime(); 99 | var dt = now - n._last; 100 | if(!n.update(dt)) { 101 | remove.push(n); 102 | } 103 | n._last = now; 104 | }); 105 | for(var r in remove) { 106 | var o = remove[r]; 107 | timer.remove(o); 108 | o._timer = null; 109 | } 110 | if(timer._children.length) requestAnimFrame(timer.tick); 111 | } 112 | 113 | extend(timer, vecnik.Tree); 114 | 115 | return timer; 116 | 117 | })(); 118 | 119 | // http get 120 | vecnik.getJSON = function(url, callback) { 121 | var mygetrequest= new XMLHttpRequest(); 122 | mygetrequest.onreadystatechange = function() { 123 | if (mygetrequest.readyState == 4){ 124 | if (mygetrequest.status == 200){ 125 | callback(JSON.parse(mygetrequest.responseText)); 126 | } else { 127 | //error 128 | } 129 | } 130 | }; 131 | mygetrequest.open("GET", url, true) 132 | mygetrequest.send(null) 133 | } 134 | 135 | /* 136 | var c = prop({ 137 | x: 0, 138 | y: 0 139 | }) 140 | c.animate({ 141 | x: 1, 142 | y: 1 143 | }) 144 | function anim() { 145 | 146 | } 147 | 148 | */ 149 | -------------------------------------------------------------------------------- /manhattanhenge/src/geo.js: -------------------------------------------------------------------------------- 1 | 2 | vecnik.geo = function() { 3 | 4 | var _x, _y, _xt, _yt, _type, _transform; 5 | var _matrix = new mat2(); 6 | 7 | // identity 8 | _transform = function(v) { return v; } 9 | 10 | function geo(data) { 11 | } 12 | 13 | function alloc(len) { 14 | _x = new Float32Array(len) 15 | _y = new Float32Array(len) 16 | _xt = new Float32Array(len) 17 | _yt = new Float32Array(len) 18 | } 19 | 20 | function _map(coordinates) { 21 | var c = coordinates; 22 | alloc(c.length); 23 | for(var i = 0; i < c.length; ++i) { 24 | var t = _transform(c[i]); 25 | _x[i] = t[0]; 26 | _y[i] = t[1]; 27 | } 28 | }; 29 | 30 | geo.matrix = function(m) { 31 | if(!arguments.length) return _matrix; 32 | _matrix = m; 33 | this.each(function(g) { 34 | //todo multiply 35 | g.matrix(m); 36 | }); 37 | return geo; 38 | } 39 | 40 | geo.x = function() { 41 | var _m = _matrix._m; 42 | for(var i = 0, len = _x.length; i < len; ++i) { 43 | _xt[i] = _m[0]*_x[i] + _m[6]; 44 | } 45 | return _xt; 46 | } 47 | 48 | geo.y = function() { 49 | var _m = _matrix._m; 50 | for(var i = 0, len = _y.length; i < len; ++i) { 51 | _yt[i] = _m[1*3 + 1]*_y[i] + _m[7]; 52 | } 53 | return _yt; 54 | } 55 | 56 | var conversion = { 57 | Point: function(coordinates) { 58 | alloc(1); 59 | var t = _transform(coordinates); 60 | _x[0] = t[0]; 61 | _y[0] = t[1]; 62 | }, 63 | LineString: _map, 64 | Polygon: function(coordinates) { _map(coordinates[0]); }, 65 | MultiPoint: _map, 66 | MultiPolygon: function(polygons) { 67 | for(var i = 0; i < polygons.length; ++i) { 68 | geo.add(vecnik.geo().transform(_transform).parseGeoJSON({ 69 | type: 'Polygon', 70 | coordinates: polygons[i] 71 | })); 72 | } 73 | } 74 | }; 75 | 76 | geo.parseGeoJSON = function(geometry) { 77 | if(geometry.features) { 78 | for(var i in geometry.features) { 79 | var g = geometry.features[i]; 80 | this.add(vecnik.geo().transform(_transform).parseGeoJSON(g)); 81 | } 82 | } else { 83 | if(geometry.type === "Feature") { 84 | _metadata = geometry.properties; 85 | geometry = geometry.geometry; 86 | } 87 | _type = geometry.type; 88 | var conversor = conversion[_type]; 89 | if(conversor) { 90 | conversor(geometry.coordinates); 91 | } 92 | } 93 | return geo; 94 | } 95 | 96 | geo.transform = function(tr) { 97 | _transform = tr; 98 | return geo; 99 | } 100 | 101 | geo.type = function(t) { 102 | if(!arguments.length) return _type; 103 | _type = t; 104 | return geo; 105 | } 106 | 107 | geo.metadata = function() { 108 | return _metadata; 109 | } 110 | 111 | extend(geo, vecnik.Tree); 112 | 113 | return geo; 114 | 115 | } 116 | -------------------------------------------------------------------------------- /manhattanhenge/src/latlng.js: -------------------------------------------------------------------------------- 1 | 2 | vecnik.LatLng = function(lat, lng) { 3 | this.x = lng; 4 | this.y = lat; 5 | } 6 | 7 | vecnik.LatLng.prototype = vecnik.vec2(0, 0); 8 | 9 | vecnik.extend(vecnik.LatLng.prototype, { 10 | 11 | lat: function() { 12 | return this.y; 13 | }, 14 | 15 | lon: function() { 16 | return this.x; 17 | } 18 | 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /manhattanhenge/src/map.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function dragger(el) { 4 | 5 | var self = this; 6 | var dragging = false; 7 | var x, y; 8 | 9 | el.ontouchstart = el.onmousedown = function(e) { 10 | dragging = true; 11 | if (e.touches) { 12 | var p = e.touches[0]; 13 | x = p.pageX; 14 | y = p.pageY; 15 | } else { 16 | x = e.clientX; 17 | y = e.clientY; 18 | } 19 | self.emit('startdrag', x, y); 20 | }; 21 | 22 | el.ontouchmove = el.onmousemove = function(e) { 23 | var xx, yy; 24 | if(!dragging) return; 25 | if (e.touches) { 26 | var p = e.touches[0]; 27 | xx = p.pageX; 28 | yy = p.pageY; 29 | } else { 30 | xx = e.clientX; 31 | yy = e.clientY; 32 | } 33 | self.emit('move', xx - x, yy - y); 34 | return false; 35 | }; 36 | 37 | el.ontouchend = el.onmouseup = function(e) { 38 | dragging = false; 39 | }; 40 | } 41 | 42 | dragger.prototype = new vecnik.Events(); 43 | 44 | vecnik.layer = function(size) { 45 | var canvas = this.canvas = document.createElement('canvas'); 46 | canvas.style.padding = '0'; 47 | canvas.style.margin= '0'; 48 | canvas.style.position = 'absolute'; 49 | canvas.width = size.x; 50 | canvas.height = size.y; 51 | canvas.style.top = 0; 52 | canvas.style.left = 0; 53 | 54 | var ctx = canvas.getContext( '2d' ); 55 | 56 | var _renderer = vecnik.renderer(ctx); 57 | 58 | var translate = {x: 0, y: 0}; 59 | var scale = {x: 1, y: 1}; 60 | 61 | function layer() { } 62 | 63 | layer.canvas = function() { 64 | return canvas; 65 | } 66 | 67 | layer.renderer = function(r) { 68 | if(!arguments.length) return _renderer; 69 | _renderer = r; 70 | return layer; 71 | } 72 | 73 | layer.translate = function(x, y) { 74 | translate.x = x; 75 | translate.y = y; 76 | return layer; 77 | } 78 | 79 | layer.scale = function(sx, sy) { 80 | scale.x = sx; 81 | scale.y = sy; 82 | } 83 | 84 | layer.clear = function() { 85 | canvas.width = canvas.width; 86 | } 87 | 88 | layer.render = function() { 89 | layer.clear(); 90 | ctx.translate(size.x>>1, size.y>>1); 91 | //ctx.scale(scale.x, scale.y); 92 | //ctx.translate(translate.x, translate.y); 93 | this.each(function(geo) { 94 | var m = geo.matrix(); 95 | m.scale(scale.x, scale.y) 96 | .translate(translate.x, translate.y); 97 | geo.matrix(m); 98 | _renderer(geo); 99 | }); 100 | /* 101 | this.each(function() { 102 | }); 103 | ctx.fillRect(-30, -30, 60, 60); 104 | */ 105 | } 106 | 107 | extend(layer, vecnik.Tree); 108 | return layer; 109 | 110 | } 111 | 112 | vecnik.map = function(_el) { 113 | 114 | var layers = []; 115 | var center = { x: 0, y: 0}; 116 | var zoom = target_zoom = 1; 117 | var el = document.createElement('div'); 118 | var width = _el.offsetWidth >> 0; 119 | var height = _el.offsetHeight >> 0; 120 | 121 | _el.appendChild(el); 122 | el.style.padding = '0'; 123 | el.style.margin= '0'; 124 | el.style.position = 'relative'; 125 | el.style.width = width + "px"; 126 | el.style.height = height + "px"; 127 | el.style.top = 0; 128 | el.style.left = 0; 129 | 130 | function map() {} 131 | extend(map, vecnik.Tree); 132 | 133 | var drag_init = {x:0, y:0}; 134 | var target_center = {x: 0, y: 0}; 135 | var drag = new dragger(el); 136 | drag.on('startdrag', function() { 137 | drag_init.x = center.x; 138 | drag_init.y = center.y; 139 | }); 140 | 141 | el.ondblclick = function(e) { 142 | map.zoom(zoom + 1); 143 | if (e.touches) { 144 | var p = e.touches[0]; 145 | x = p.pageX; 146 | y = p.pageY; 147 | } else { 148 | var s = 1.0/Math.pow(2, zoom); 149 | target_center.x = -s*(e.clientX - (width/2)) + center.x; 150 | target_center.y = -s*(e.clientY - (height/2)) + center.y; 151 | } 152 | vecnik.timer(map); 153 | } 154 | 155 | el.onmousewheel = function(e) { 156 | if(e.wheelDeltaY > 0) { 157 | map.zoom(map.zoom() + 0.3); 158 | } else { 159 | map.zoom(map.zoom() - 0.3); 160 | } 161 | } 162 | 163 | drag.on('move', function(dx, dy) { 164 | var s = 1.0/Math.pow(2, zoom); 165 | target_center.x = drag_init.x + dx*s; 166 | target_center.y = drag_init.y + dy*s; 167 | vecnik.timer(map); 168 | }); 169 | 170 | map.transform = function(tr) { 171 | transform = tr; 172 | return map; 173 | } 174 | 175 | map.size = function() { 176 | return { x: width, y: height}; 177 | } 178 | 179 | map.zoom = function(z) { 180 | if(!arguments.length) return zoom; 181 | target_zoom = z; 182 | vecnik.timer(map); 183 | return map; 184 | } 185 | 186 | map.addLayer = function(layer) { 187 | el.appendChild(layer.canvas()); 188 | layers.push(layer); 189 | map.add(layer); 190 | } 191 | 192 | map.update = function(dt) { 193 | var c = center; 194 | var t = target_center; 195 | var dx = t.x - c.x; 196 | var dy = t.y - c.y; 197 | c.x += dx*0.1; 198 | c.y += dy*0.1; 199 | 200 | zoom += (target_zoom - zoom) * 0.01 * dt; 201 | 202 | this.each(function(layer) { 203 | layer.translate(c.x, c.y); 204 | var s = Math.pow(2, zoom); 205 | layer.scale(s, s); 206 | }) 207 | map.render(); 208 | 209 | return Math.abs(dx) + Math.abs(dy) > 0.001 || 210 | Math.abs(target_zoom - zoom) > 0.1; 211 | } 212 | 213 | map.render = function() { 214 | this.each(function(layer) { 215 | layer.render(); 216 | }) 217 | } 218 | 219 | return map; 220 | } 221 | -------------------------------------------------------------------------------- /manhattanhenge/src/math.js: -------------------------------------------------------------------------------- 1 | 2 | function degreesToRadians(deg) { 3 | return deg * (Math.PI / 180); 4 | } 5 | 6 | function radiansToDegrees(rad) { 7 | return rad / (Math.PI / 180); 8 | } 9 | 10 | 11 | function bound(value, opt_min, opt_max) { 12 | if (opt_min != null) value = Math.max(value, opt_min); 13 | if (opt_max != null) value = Math.min(value, opt_max); 14 | return value; 15 | } 16 | -------------------------------------------------------------------------------- /manhattanhenge/src/mercator.js: -------------------------------------------------------------------------------- 1 | 2 | //======================================== 3 | // Mercator projection 4 | //======================================== 5 | 6 | vecnik.mercator = function() { 7 | 8 | var TILE_SIZE = 256; 9 | var pixelOrigin_ = [TILE_SIZE / 2, TILE_SIZE / 2 ]; 10 | var pixelsPerLonDegree_ = TILE_SIZE / 360; 11 | var pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI); 12 | 13 | function _mercator(latLng, opt_point) { 14 | var me = this; 15 | var point = opt_point || [0, 0]; 16 | var origin = pixelOrigin_; 17 | 18 | point[0] = origin[0] + latLng[0] * pixelsPerLonDegree_; 19 | 20 | // NOTE(appleton): Truncating to 0.9999 effectively limits latitude to 21 | // 89.189. This is about a third of a tile past the edge of the world 22 | // tile. 23 | var siny = bound(Math.sin(degreesToRadians(latLng[1])), -0.9999, 24 | 0.9999); 25 | point[1] = origin[1] + 0.5 * Math.log((1 + siny) / (1 - siny)) * 26 | -pixelsPerLonRadian_; 27 | return point; 28 | } 29 | 30 | return _mercator; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /manhattanhenge/src/renderer.canvas.js: -------------------------------------------------------------------------------- 1 | 2 | vecnik.renderer = function(ctx) { 3 | 4 | var _ctx = ctx; 5 | var _shader; 6 | 7 | function render(geo) { 8 | _render(geo); 9 | } 10 | 11 | var self = render; 12 | 13 | var _primitive_render = { 14 | 15 | Point: function(x, y) { 16 | _ctx.save(); 17 | var radius = 2; 18 | _ctx.translate(x, y); 19 | _ctx.beginPath(); 20 | _ctx.arc(radius, radius, radius, 0, Math.PI * 2, true); 21 | _ctx.closePath(); 22 | _ctx.fill(); 23 | _ctx.stroke(); 24 | _ctx.restore(); 25 | }, 26 | 27 | MultiPoint: function(x, y) { 28 | var prender = _primitive_render.Point; 29 | for(var i = 0, len = x.length; i < len; ++i) { 30 | prender(x[i], y[i]); 31 | } 32 | }, 33 | 34 | Polygon: function(x, y) { 35 | _ctx.beginPath(); 36 | _ctx.moveTo(x[0], y[0]); 37 | for(var i = 0, len = x.length; i < len; ++i) { 38 | _ctx.lineTo(x[i], y[i]); 39 | } 40 | _ctx.closePath(); 41 | //_ctx.fill(); 42 | }, 43 | 44 | LineString: function(x, y) { 45 | _ctx.beginPath(); 46 | _ctx.moveTo(x[0], y[0]); 47 | for(var i = 0, len = x.length; i < len; ++i) { 48 | _ctx.lineTo(x[i], y[i]); 49 | } 50 | //_ctx.stroke(); 51 | } 52 | }; 53 | 54 | render.primitiveRender = function(r) { 55 | if(!arguments.length) return _primitive_render; 56 | _primitive_render = r; 57 | return render; 58 | } 59 | 60 | render.shader = function(shader) { 61 | _shader = shader; 62 | } 63 | 64 | function _render(geo) { 65 | var primitive_type; 66 | if(!geo.children().length) { 67 | var primitive_type = geo.type(); 68 | var renderer = _primitive_render[primitive_type]; 69 | if(renderer) { 70 | var is_active = true; 71 | if(_shader) { 72 | is_active = _shader.needs_render(geo.metadata(), _ctx, primitive_type); 73 | if(is_active) { 74 | _shader.reset(ctx, primitive_type); 75 | _shader.apply(ctx, geo.metadata(), _ctx); 76 | } 77 | } 78 | if (is_active) { 79 | renderer(geo.x(), geo.y()); 80 | if(_shader.stroke()) { 81 | _ctx.stroke(); 82 | } 83 | if(_shader.fill()) { 84 | _ctx.fill(); 85 | } 86 | } 87 | } 88 | } else { 89 | geo.each(_render); 90 | } 91 | }; 92 | 93 | return render; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /manhattanhenge/src/shader.js: -------------------------------------------------------------------------------- 1 | //======================================== 2 | // shader 3 | //======================================== 4 | 5 | vecnik.shader = function(sh) { 6 | 7 | var mapper = { 8 | 'point-color': 'fillStyle', 9 | 'line-color': 'strokeStyle', 10 | 'line-width': 'lineWidth', 11 | 'line-opacity': 'globalAlpha', 12 | 'polygon-fill': 'fillStyle', 13 | 'polygon-opacity': 'globalAlpha' 14 | }; 15 | 16 | 17 | var compiled = {}; 18 | var _shader_src = null; 19 | 20 | function _shader(canvas_ctx, data, render_context) { 21 | _shader.apply(canvas_ctx, data, render_context); 22 | } 23 | 24 | //extend(_shader, vecnik.Event); 25 | 26 | 27 | _shader.compile = function(shader) { 28 | if(typeof shader === 'string') { 29 | shader = eval("(function() { return " + shader +"; })()"); 30 | } 31 | _shader_src = shader; 32 | for(var attr in shader) { 33 | var c = mapper[attr]; 34 | if(c) { 35 | compiled[c] = eval("(function() { return shader[attr]; })();"); 36 | } 37 | } 38 | 39 | //_shader.emit('compiled'); 40 | return _shader; 41 | }; 42 | 43 | var needed_settings = { 44 | 'LineString': [ 45 | 'line-color', 46 | 'line-width', 47 | 'line-opacity' 48 | ], 49 | 'Polygon': [ 50 | 'polygon-fill', 51 | 'line-width' 52 | ], 53 | 'MultiPolygon': [ 54 | 'polygon-fill' 55 | ] 56 | }; 57 | var defaults = { 58 | 'LineString': { 59 | 'strokeStyle': '#000', 60 | 'lineWidth': 1, 61 | 'globalAlpha': 1.0, 62 | 'lineCap': 'round' 63 | }, 64 | 'Polygon': { 65 | 'strokeStyle': '#000', 66 | 'lineWidth': 1, 67 | 'globalAlpha': 1.0 68 | }, 69 | 'MultiPolygon': { 70 | 'strokeStyle': '#000', 71 | 'lineWidth': 1, 72 | 'globalAlpha': 1.0 73 | } 74 | }; 75 | 76 | _shader.needs_render = function(data, render_context, primitive_type) { 77 | var variables = needed_settings[primitive_type]; 78 | var shader = compiled; 79 | for(var attr in variables) { 80 | var style_attr = variables[attr]; 81 | var attr_present = _shader_src[style_attr]; 82 | if(attr_present !== undefined) { 83 | var fn = shader[mapper[style_attr]]; 84 | if(typeof fn === 'function') { 85 | fn = fn(data, render_context); 86 | } 87 | if(fn !== null && fn !== undefined) { 88 | return true; 89 | } 90 | } 91 | } 92 | return false; 93 | } 94 | 95 | _shader.reset = function(ctx, primitive_type) { 96 | var def = defaults[primitive_type]; 97 | for(var attr in def) { 98 | ctx[attr] = def[attr]; 99 | } 100 | } 101 | 102 | _shader.apply = function(canvas_ctx, data, render_context) { 103 | var shader = compiled; 104 | for(var attr in shader) { 105 | var fn = shader[attr]; 106 | if(typeof fn === 'function') { 107 | fn = fn(data, render_context); 108 | } 109 | if(fn !== null && canvas_ctx[attr] != fn) { 110 | canvas_ctx[attr] = fn; 111 | } 112 | } 113 | }; 114 | 115 | _shader.stroke = function() { 116 | return _shader_src['line-width'] != undefined; 117 | } 118 | 119 | _shader.fill = function() { 120 | return _shader_src['polygon-fill'] != undefined; 121 | } 122 | 123 | _shader.compile(sh) 124 | 125 | return _shader; 126 | 127 | } 128 | -------------------------------------------------------------------------------- /manhattanhenge/src/tween.js: -------------------------------------------------------------------------------- 1 | 2 | function tween() { 3 | 4 | function _tween() {} 5 | 6 | _tween.update = function(dt) { 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /manhattanhenge/src/vec2.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function _vec2(x, y) { 4 | this.x = x; 5 | this.y = y; 6 | this.length = function() { 7 | return Math.sqrt(this.x*this.x+this.y*this.y); 8 | } 9 | this.len2 = function() { 10 | return this.x*this.x+this.y*this.y; 11 | } 12 | this.add = function(v) { 13 | this.x += v.x; 14 | this.y += v.y; 15 | } 16 | this.clone = function() { 17 | return new vec2(this.x, this.y); 18 | } 19 | } 20 | 21 | var add = function(v1, v2) { 22 | return vec2(v1.x+v2.x, v1.y+v2.y); 23 | } 24 | 25 | var sub = function(v1, v2) { 26 | return vec2(v1.x-v2.x, v1.y-v2.y); 27 | } 28 | 29 | var mul = function(s, v1) { 30 | return vec2(s*v1.x, s*v1.y); 31 | } 32 | 33 | var dot = function(v1, v2) { 34 | return v1.x*v2.x + v1.y*v2.y; 35 | } 36 | 37 | var normalize = function(v1) { 38 | var m = v1.length(); 39 | return vec2(v1.x/m, v1.y/m); 40 | } 41 | 42 | function vfn(v, fn) { 43 | return vec2(fn(v.x), fn(v.y)); 44 | } 45 | 46 | function normal(v) { 47 | return vec2(-v.y, v.x); 48 | } 49 | 50 | vecnik.vec2 = function vec2(x, y) { 51 | return new _vec2(x, y); 52 | } 53 | 54 | vecnik.extend(vecnik.vec2, { 55 | add: add, 56 | sub: sub, 57 | dot: dot, 58 | mul: mul, 59 | normalize: normalize, 60 | vfn: vfn, 61 | normal: normal 62 | }); 63 | 64 | 65 | function mat2(m) { 66 | m = m || [ 1, 0, 0, 0 , 1, 0, 0, 0, 1 ]; 67 | this._m = new Float32Array(3*3) 68 | for(var i = 0; i < 3*3; ++i) { 69 | this._m[i] = m[i]; 70 | } 71 | } 72 | 73 | 74 | mat2.prototype = { 75 | 76 | identity: function() { 77 | m = [ 1, 0, 0, 0 , 1, 0, 0, 0, 1 ]; 78 | for(var i = 0; i < 3*3; ++i) { 79 | this._m[i] = m[i]; 80 | } 81 | }, 82 | 83 | set: function(i, j, v) { 84 | this._m[j*3 + i] = v; 85 | return this; 86 | }, 87 | 88 | get: function(i, j) { 89 | return this._m[j*3 + i]; 90 | }, 91 | 92 | translate: function(sx, sy) { 93 | return this 94 | .set(0, 2, sx) 95 | .set(1, 2, sy); 96 | }, 97 | 98 | scale: function(sx, sy) { 99 | return this 100 | .set(0, 0, sx) 101 | .set(1, 1, sy); 102 | } 103 | } 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /point-clustering/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet multilayer example | CartoDB.js 5 | 6 | 7 | 8 | 15 | 16 | 17 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 43 | 44 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /private-maps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Private maps 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | 23 |
24 | 94 | 95 | -------------------------------------------------------------------------------- /scroll-story/basic/css/dark-theme.css: -------------------------------------------------------------------------------- 1 | /* Change the styles below in order to customize your template */ 2 | 3 | body{font-family: Helvetica, Arial; font-weight: regular; font-size: 15px; color: #CBCBCB; background-color: #333; margin: 0;} 4 | h1{font-weight: bold; font-size: 31px; letter-spacing: -1px; color: #FFF; line-height: 33px;} 5 | h3{font-weight: bold; font-size: 12px; color: #777; text-transform: uppercase; margin: 18px 0 0 0;} 6 | p{margin: 8px 0 20px 0; line-height: 18px;} 7 | a, a:visited{color: #72B6E5; text-decoration: none;} 8 | a:hover{text-decoration: underline;} 9 | 10 | .wrapper{display: block; padding: 4px 30px 0 30px;} 11 | 12 | .map{background-color:#eee; position: absolute; top: 0; left: 0; bottom: 0; width: 67%; *height:100%;} 13 | .sidepanel{background-color:#333; position: absolute; top: 0; right: 0; bottom: 0; width: 44%; height: 100%; overflow: auto;} 14 | 15 | .context{font-family: Helvetica, Arial; font-size: 13px; color: #999; padding: 10px 0 0 0;} 16 | .subheader{border-bottom: 1px solid #555;} 17 | .footer{border-top: 1px solid #555; margin-top: 30px;} 18 | .titleBlock{text-align: right;} -------------------------------------------------------------------------------- /scroll-story/basic/css/light-theme.css: -------------------------------------------------------------------------------- 1 | /* Change the styles below in order to customize your template */ 2 | 3 | body{font-family: Helvetica, Arial; font-weight: regular; font-size: 15px; color: #555; background-color: #FFF; margin: 0;} 4 | h1{font-weight: bold; font-size: 31px; letter-spacing: -1px; color: #333; line-height: 33px;} 5 | h3{font-weight: bold; font-size: 12px; color: #CCC; text-transform: uppercase; margin: 10px 0 0 0;} 6 | p{margin: 8px 0 20px 0; line-height: 18px;} 7 | a, a:visited{color: #397DB8; text-decoration: none;} 8 | a:hover{text-decoration: underline;} 9 | 10 | .wrapper{display: block; padding: 4px 30px 0 30px;} 11 | 12 | .map{background-color:#eee; position: absolute; top: 0; left: 0; bottom: 0; width: 67%; *height:100%;} 13 | .sidepanel{background-color:#FFF; position: absolute; top: 0; right: 0; bottom: 0; width: 33%; height: 100%; overflow: auto;} 14 | 15 | .context{font-family: Helvetica, Arial; font-size: 13px; color: #999; padding: 10px 0 0 0;} 16 | .subheader{border-bottom: 1px solid #ddd;} 17 | .footer{border-top: 1px solid #ddd; margin-top: 30px;} 18 | .titleBlock{text-align: right;} -------------------------------------------------------------------------------- /scroll-story/basic/css/makeitresponsive.css: -------------------------------------------------------------------------------- 1 | /* Here are the styles that makes the template responsive */ 2 | 3 | @media only screen and (max-width: 768px) { 4 | .map{position: inherit; height: 400px; width: 100%; display: block;} 5 | .sidepanel{position: inherit; width: 100%;} 6 | } 7 | 8 | @media only screen and (max-width: 480px) { 9 | .map {height: 300px;} 10 | } 11 | -------------------------------------------------------------------------------- /scroll-story/basic/js/VideoOverlay.js: -------------------------------------------------------------------------------- 1 | L.VideoOverlay = L.Class.extend({ 2 | includes: L.Mixin.Events, 3 | 4 | options: { 5 | opacity: 1 6 | }, 7 | 8 | initialize: function (topLeft, size, options) { // (String, LatLngBounds, Object) 9 | // this._video = video; 10 | // this._ctx = video.getContext('2d'); 11 | // this._bounds = L.latLngBounds(bounds); 12 | this._inView = true; 13 | this._topLeft = topLeft; 14 | this._size = size; 15 | this._src = options.src; 16 | this._origSize = size; 17 | L.setOptions(this, options); 18 | 19 | }, 20 | getContext: function(){ 21 | return this._context; 22 | }, 23 | getVideo: function(){ 24 | return this._video; 25 | }, 26 | onAdd: function (map) { 27 | this._map = map; 28 | 29 | if (!this._video) { 30 | this._initImage(); 31 | } 32 | 33 | map._panes.overlayPane.appendChild(this._video); 34 | 35 | map.on('viewreset', this._reset, this); 36 | 37 | if (map.options.zoomAnimation && L.Browser.any3d) { 38 | map.on('zoomanim', this._animateZoom, this); 39 | } 40 | 41 | this._reset(); 42 | }, 43 | 44 | onRemove: function (map) { 45 | map.getPanes().overlayPane.removeChild(this._video); 46 | 47 | map.off('viewreset', this._reset, this); 48 | 49 | if (map.options.zoomAnimation) { 50 | map.off('zoomanim', this._animateZoom, this); 51 | } 52 | }, 53 | 54 | addTo: function (map) { 55 | map.addLayer(this); 56 | return this; 57 | }, 58 | 59 | setOpacity: function (opacity) { 60 | this.options.opacity = opacity; 61 | this._updateOpacity(); 62 | return this; 63 | }, 64 | 65 | // TODO remove bringToFront/bringToBack duplication from TileLayer/Path 66 | bringToFront: function () { 67 | if (this._video) { 68 | this._map._panes.overlayPane.appendChild(this._video); 69 | } 70 | return this; 71 | }, 72 | 73 | bringToBack: function () { 74 | var pane = this._map._panes.overlayPane; 75 | if (this._video) { 76 | pane.insertBefore(this._video, pane.firstChild); 77 | } 78 | return this; 79 | }, 80 | 81 | _initImage: function () { 82 | this._video = L.DomUtil.create('iframe', 'leaflet-image-layer'); 83 | this._video.src = this._src; 84 | this._width = this._video.width = this._size.x; 85 | this._height = this._video.height = this._size.y; 86 | 87 | if (this._map.options.zoomAnimation && L.Browser.any3d) { 88 | L.DomUtil.addClass(this._video, 'leaflet-zoom-animated'); 89 | } else { 90 | L.DomUtil.addClass(this._video, 'leaflet-zoom-hide'); 91 | } 92 | 93 | this._updateOpacity(); 94 | 95 | //TODO createImage util method to remove duplication 96 | L.extend(this._video, { 97 | galleryimg: 'no', 98 | onselectstart: L.Util.falseFn, 99 | onmousemove: L.Util.falseFn, 100 | onload: L.bind(this._onImageLoad, this), 101 | // src: this._video.toDataURL() 102 | }); 103 | }, 104 | _outOfFocus: function() { 105 | if (this._inView){ 106 | this._returnZoom = this._map.getZoom() - 1; 107 | this._oldOpacity = this.options.opacity; 108 | this.options.opacity = 0; 109 | this._updateOpacity(); 110 | this._inView = false; 111 | } 112 | }, 113 | _inFocus: function() { 114 | if (this._returnZoom >= this._map.getZoom()) { 115 | this.options.opacity = this._oldOpacity; 116 | this._updateOpacity(); 117 | this._inView = true; 118 | } 119 | }, 120 | _animateZoom: function (e) { 121 | var map = this._map, 122 | image = this._video, 123 | scale = map.getZoomScale(e.zoom), 124 | topLeft = map._latLngToNewLayerPoint(this._topLeft, e.zoom, e.center); 125 | 126 | 127 | var size = {x: scale*this._video.width, y: scale*this._video.height}; 128 | 129 | if (size.x < 10) {this._outOfFocus(); return;} 130 | if (size.x < 10) {this._outOfFocus(); return;} 131 | if (!this._inView){ 132 | this._inFocus(); 133 | } else { 134 | this._size = size; 135 | origin = topLeft._add({x: this._size.x * (1 / 2) * (1 - 1/scale), y: this._size.y * (1 / 2) * (1 - 1/scale)}) 136 | 137 | image.style[L.DomUtil.TRANSFORM] = 138 | L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; 139 | 140 | this._width = this._video.width = this._size.x; 141 | this._height = this._video.height = this._size.y ; 142 | } 143 | }, 144 | 145 | _reset: function (size) { 146 | if(this._inView){ 147 | var image = this._video, 148 | topLeft = this._map.latLngToLayerPoint(this._topLeft) 149 | size = this._size; 150 | 151 | L.DomUtil.setPosition(image, topLeft); 152 | 153 | image.style.width = size.x + 'px'; 154 | image.style.height = size.y + 'px'; 155 | console.log('reset') 156 | } 157 | }, 158 | 159 | _onImageLoad: function () { 160 | this.fire('load'); 161 | }, 162 | 163 | _updateOpacity: function () { 164 | L.DomUtil.setOpacity(this._video, this.options.opacity); 165 | } 166 | }); 167 | 168 | L.videoOverlay = function (url, bounds, options) { 169 | return new L.VideoOverlay(url, bounds, options); 170 | }; -------------------------------------------------------------------------------- /scroll-story/pluto/css/makeitresponsive.css: -------------------------------------------------------------------------------- 1 | /* Here are the styles that makes the template responsive */ 2 | 3 | @media only screen and (max-width: 768px) { 4 | .map{position: inherit; height: 400px; width: 100%; display: block;} 5 | .sidepanel{position: inherit; width: 100%;} 6 | } 7 | 8 | @media only screen and (max-width: 480px) { 9 | .map {height: 300px;} 10 | } 11 | -------------------------------------------------------------------------------- /scroll-story/pluto/css/pluto-theme.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | body{font-family:normal "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: regular; font-size: 22px; color: #000; background-color: white; margin: 0;} 4 | h1{font-weight: bold; text-transform: uppercase; font-size: 46px; letter-spacing: -1px; color: #000; line-height: 48px;} 5 | h3{font-weight: bold; font-size: 12px; color: #000; text-transform: uppercase; margin: 18px 0 0 0;} 6 | p{margin: 8px 0 20px 0; line-height: 25px; ;} 7 | p.source {text-align: right;} 8 | p.mega{font-size: 70px; line-height: 74px;} 9 | b {color: black; font-size: 1.35em;} 10 | a, a:visited{color: #3f85c2; text-decoration: none;} 11 | a:hover{text-decoration: underline;} 12 | 13 | .wrapper{display: block; padding: 4px 30px 0 30px;} 14 | 15 | .map{background-color: transparent; position: absolute; top: 0; left: 0; bottom: 0; width: 75%; height:100%;} 16 | 17 | .sidepanel{background-color:transparent; position: absolute; top: 0; right: 0; bottom: 0; width: 44%; height: 100%; overflow: auto; 18 | background-image: linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 30%, rgba(255,255,255,1) 50%); 19 | background-image: -o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 30%, rgba(255,255,255,1) 50%); 20 | background-image: -moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 30%, rgba(255,255,255,1) 50%); 21 | background-image: -ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 30%, rgba(255,255,255,1) 50%); 22 | background-image: -webkit-linear-gradient(left, rgba(255,255,255,0.0001) 0%, rgba(255,255,255,1) 30%, rgba(255,255,255,1) 50%); 23 | } 24 | 25 | /*.context{width: 100%; font-family: 'Goblin One', cursive; font-size: 13px; color: #000; padding: 20px 0 0 0;}*/ 26 | .context.subheader{font-size: 0.75em; margin-bottom: 80px; line-height: 10px; text-align: right; } 27 | .footer{margin-top: 10px; font-size: 0.75em; line-height: 10px; text-align: right; float: right; cursor: pointer;} 28 | .titleBlock{text-align: right;} 29 | #back-button{position: absolute; position: absolute; bottom: 10px; right: 110px; font-size: 1.2em; cursor: pointer;} 30 | #next-button{position: absolute; position: absolute; bottom: 10px; right: 20px; font-size: 1.2em; cursor: pointer;} -------------------------------------------------------------------------------- /security-definer/README.md: -------------------------------------------------------------------------------- 1 | ## Create private visualizations 2 | 3 | ### Row level security 4 | 5 | This will show you how to create visualizations with row-level control over which users can see what. There are two examples for row-level security on single and private multiple tables, a simple [Single table with row-level permissions](row-level/README.md) example and then building on that a [Multiple tables with row-level permissions](row-level/MultiTable.md) example. They are just what they say, examples that have a single private table or multiple private tables that can be accessed by defined users with known keys. 6 | 7 | #### Features 8 | 9 | * Multiple users with unique keys 10 | * Ability to cycle keys 11 | * Group level permissions so multiple users can share the same permissions 12 | * Ability to use data from private maps in both visualizations and over the SQL API 13 | 14 | ### Table level security 15 | 16 | There is also an example for creating table-level group security, such that a member of a group either has access to a complete table or no access. This is easier to manage than the row-level security and will fit many use cases. You can find that example here, [Table level permissions](table-level/README.md). 17 | 18 | -------------------------------------------------------------------------------- /security-definer/row-level/MultiTable.md: -------------------------------------------------------------------------------- 1 | ## Multi-table row-level security example 2 | 3 | If you want to create maps from more than one private table we can easily modify the simple example in [README.md](README.md) to do the trick. 4 | 5 | ## Use a new private table of data. 6 | 7 | 1. Dubplicate ```private_poi``` from the first tutorial 8 | 2. Rename it to ```new_table_name``` 9 | 10 | ## Create a new generic security checking function 11 | 12 | ```sql 13 | 14 | CREATE OR REPLACE FUNCTION AXHGroupCheck(username text, secret text) 15 | RETURNS integer 16 | AS $$ 17 | DECLARE 18 | sql text; 19 | table_id INT; 20 | group_info RECORD; 21 | BEGIN 22 | 23 | -- Check that our username and secret are valid 24 | sql := 'SELECT group_id FROM public.private_user_list WHERE lower(username) = lower($1) AND secret = $2'; 25 | EXECUTE sql USING username, secret INTO group_info; 26 | 27 | IF group_info IS NULL THEN 28 | RAISE EXCEPTION 'Authorization failed for user %', username; 29 | END IF; 30 | 31 | RETURN group_info.group_id; 32 | 33 | END; 34 | $$ LANGUAGE 'plpgsql'; 35 | ``` 36 | 37 | ## Add new tables with private data 38 | 39 | You can now add any new private table with group defined data access to your account. To do so assuming your new data table is named ```new_table_name```, 40 | 41 | 1. Create the table with the desired tablename, new_table_name 42 | 2. Ensure that the new table has a column for group IDS 43 | ```sql 44 | 45 | ALTER TABLE new_table_name ADD COLUMN group_id INT[] 46 | ``` 47 | 3. Update row level permission with SQL, e.g. 48 | ```sql 49 | 50 | UPDATE new_table_name SET group_id = '{1}' 51 | ``` 52 | 4. Add a new trigger to the table for public cache invalidation 53 | 54 | 55 | ## Add new function and trigger for each private table you add 56 | 57 | 58 | ## Update our security definer 59 | 60 | ```sql 61 | 62 | CREATE OR REPLACE FUNCTION AXHNewTableName(username text, secret text) 63 | RETURNS SETOF new_table_name 64 | AS $$ 65 | DECLARE 66 | sql text; 67 | table_id INT; 68 | group_id INT; 69 | val_list RECORD; 70 | BEGIN 71 | -- Check that our username and secret are valid 72 | group_id := AXHGroupCheck(username, secret); 73 | 74 | sql := 'SELECT * FROM public.new_table_name WHERE '||group_id||' = ANY(group_id)'; 75 | FOR val_list IN EXECUTE sql 76 | LOOP 77 | RETURN NEXT val_list; 78 | END LOOP; 79 | RETURN; 80 | END; 81 | $$ LANGUAGE 'plpgsql' SECURITY DEFINER; 82 | ``` 83 | 84 | You will need to modify this function for each new private table you create. You will want a new function namer, here I called it ```AXHNewTableName``` because my table is named new_table_name. Also, change it internally whenever it says ```new_table_name``` to be equal to the name of your new table. 85 | 86 | Now add a trigger to the new table, 87 | 88 | ```sql 89 | 90 | CREATE TRIGGER invalidate_user_poi_from_private 91 | AFTER INSERT OR UPDATE OR DELETE ON new_table_name 92 | FOR EACH STATEMENT 93 | EXECUTE PROCEDURE AXHUpdate_Trigger(); 94 | ``` 95 | 96 | Again, you need to add a trigger for each new private table you add to your database. 97 | 98 | ## See it live 99 | 100 | You can see my running app now on 101 | 102 | [http://andrewxhill.com/cartodb-examples/security-definer/row-level/examples/multi-table-security.html](http://andrewxhill.com/cartodb-examples/security-definer/row-level/examples/multi-table-security.html) 103 | 104 | It creates a visualization from our empty dataset, then applies a query based on the username key pair provided on entry. 105 | -------------------------------------------------------------------------------- /security-definer/row-level/examples/multi-table-security.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Easy example | CartoDB.js 5 | 6 | 7 | 8 | 46 | 47 | 50 | 51 | 52 |
53 |
54 |

55 |

56 |

57 |

58 | 59 |
60 |
61 |
62 | 63 | 64 | 65 | 66 | 100 | 101 | -------------------------------------------------------------------------------- /security-definer/row-level/examples/row-level-security.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Easy example | CartoDB.js 5 | 6 | 7 | 8 | 46 | 47 | 50 | 51 | 52 |
53 |
54 |

55 |

56 |

57 |

58 | 59 |
60 |
61 |
62 | 63 | 64 | 65 | 66 | 100 | 101 | -------------------------------------------------------------------------------- /smart-markers/icon-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/smart-markers/icon-background.png -------------------------------------------------------------------------------- /smart-markers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 41 | 42 | 43 | 44 |
45 |
46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /tornado/README.md: -------------------------------------------------------------------------------- 1 | uses a partial load at a time to get 50k moving points into a d3 map 2 | -------------------------------------------------------------------------------- /tornado/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /tornado/torque/css/leaflet.ie.css: -------------------------------------------------------------------------------- 1 | .leaflet-vml-shape { 2 | width: 1px; 3 | height: 1px; 4 | } 5 | .lvml { 6 | behavior: url(#default#VML); 7 | display: inline-block; 8 | position: absolute; 9 | } 10 | 11 | .leaflet-control { 12 | display: inline; 13 | } 14 | 15 | .leaflet-popup-tip { 16 | width: 21px; 17 | _width: 27px; 18 | margin: 0 auto; 19 | _margin-top: -3px; 20 | 21 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); 22 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; 23 | } 24 | .leaflet-popup-tip-container { 25 | margin-top: -1px; 26 | } 27 | .leaflet-popup-content-wrapper, .leaflet-popup-tip { 28 | border: 1px solid #999; 29 | } 30 | .leaflet-popup-content-wrapper { 31 | zoom: 1; 32 | } 33 | 34 | .leaflet-control-zoom, 35 | .leaflet-control-layers { 36 | border: 3px solid #999; 37 | } 38 | .leaflet-control-layers-toggle { 39 | } 40 | .leaflet-control-attribution, 41 | .leaflet-control-layers, 42 | .leaflet-control-scale-line { 43 | background: white; 44 | } 45 | .leaflet-zoom-box { 46 | filter: alpha(opacity=50); 47 | } 48 | .leaflet-control-attribution { 49 | border-top: 1px solid #bbb; 50 | border-left: 1px solid #bbb; 51 | } -------------------------------------------------------------------------------- /tornado/torque/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | html, body { height:100%; width:100%; margin:0; } 3 | div#map { position:relative; height:100%; width:100%; } -------------------------------------------------------------------------------- /tornado/torque/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Living maps 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 69 | 70 | 71 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /tornado/torque/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.requestAnimationFrame; 3 | 4 | 5 | var App = { 6 | 7 | initialize: function() { 8 | this.map = L.map('map').setView( 9 | [40,-95] // london 10 | , 5); 11 | this.map.keyboard.disable(); 12 | L.tileLayer( 13 | //'http://tile.stamen.com/toner/{z}/{x}/{y}.png', { 14 | 'https://dnv9my2eseobd.cloudfront.net/v3/cartodb.map-4xtxp73f/{z}/{x}/{y}.png', { 15 | attribution: 'mapbox' 16 | }) 17 | .setOpacity(1) 18 | .addTo(this.map); 19 | 20 | this.addStreetLayer(); 21 | this.render = this.render.bind(this); 22 | this.initControls(); 23 | this.t = 0; 24 | this.speed = 0.5 25 | this.time = document.getElementById('date'); 26 | requestAnimationFrame(this.render); 27 | var self = this; 28 | 29 | this.add_debug() 30 | 31 | }, 32 | 33 | add_debug: function() { 34 | var gui = new dat.GUI(); 35 | var ro = this.layer.render_options 36 | gui.add(this, 'speed', 0, 3) 37 | gui.add(ro, 'filtered') 38 | 39 | var f2 = gui.addFolder('particles'); 40 | f2.add(ro, 'part_min_size', 0.2, 40).onChange(this.layer.precache_sprites) 41 | f2.add(ro, 'part_inc', 0, 70).onChange(this.layer.precache_sprites) 42 | f2.add(ro, 'min_alpha', 0, 0.3).onChange(this.layer.precache_sprites) 43 | f2.add(ro, 'alpha_inc', 0, 0.5).onChange(this.layer.precache_sprites) 44 | f2.add(ro, 'exp_decay', 0, 20).onChange(this.layer.precache_sprites) 45 | f2.addColor(ro, 'part_color').onChange(this.layer.precache_sprites) 46 | f2.open(); 47 | 48 | var post = gui.addFolder('Postprocess'); 49 | post.add(ro, 'post_alpha', 0, 1) 50 | post.add(ro, 'post_decay', 0, 1) 51 | post.add(ro, 'post_size', [64, 128, 256, 512, 1024]).onChange(this.layer.init_post_process) 52 | post.add(ro, 'post_process') 53 | post.open() 54 | }, 55 | 56 | set_date: function() { 57 | var real_time = this.t + this.layer.options.start_date; 58 | var date = new Date(real_time * 1000); 59 | var date_arry = date.toString().substr(4).split(' '); 60 | var pad = "00" 61 | var mins = Math.floor(real_time%60) + ""; 62 | this.time.innerHTML = 63 | Math.floor(real_time/60) + ':' + pad.substring(0, 2 - mins.length) + mins ; 64 | }, 65 | 66 | render: function() { 67 | this.layer._render(0.02); 68 | this.t += this.speed; 69 | this.layer.set_time(this.t>>0); 70 | this.set_date(); 71 | /*if(this.controls.left) { 72 | this.t = Math.max(0, this.t - 1); 73 | this.layer.set_time(this.t); 74 | this.time.innerHTML = this.t; 75 | } else if(this.controls.right) { 76 | this.t++; 77 | this.layer.set_time(this.t); 78 | this.time.innerHTML = this.t; 79 | }*/ 80 | requestAnimationFrame(this.render); 81 | if(this.t + this.layer.options.start_date > this.layer.options.end_date) { 82 | this.t = 0; 83 | } 84 | }, 85 | 86 | addStreetLayer: function() { 87 | this.layer = new StreetLayer(); 88 | //this.layer = new StreetLayerDensity(); 89 | this.map.addLayer(this.layer); 90 | }, 91 | 92 | initControls: function() { 93 | var controls = this.controls = { 94 | left: false, 95 | right: false, 96 | fire: false, 97 | up: false, 98 | down: false 99 | }; 100 | window.addEventListener('keyup', function(e) { key(e, false); }); 101 | window.addEventListener('keydown', function(e) { key(e, true);}); 102 | function key(e, w) { 103 | if(e.keyCode == 38) { controls.up= w; } 104 | else if(e.keyCode == 40) { controls.down = w; } 105 | else if(e.keyCode == 37) { controls.left = w; } 106 | else if(e.keyCode == 39) { controls.right= w; } 107 | else if(e.keyCode == 32) { controls.fire = w; } 108 | }; 109 | } 110 | 111 | } 112 | 113 | -------------------------------------------------------------------------------- /tornado/torque/js/canvas_layer.js: -------------------------------------------------------------------------------- 1 | L.CanvasLayer = L.Class.extend({ 2 | 3 | includes: [L.Mixin.Events, L.Mixin.TileLoader], 4 | 5 | options: { 6 | minZoom: 0, 7 | maxZoom: 28, 8 | tileSize: 256, 9 | subdomains: 'abc', 10 | errorTileUrl: '', 11 | attribution: '', 12 | zoomOffset: 0, 13 | opacity: 1, 14 | unloadInvisibleTiles: L.Browser.mobile, 15 | updateWhenIdle: L.Browser.mobile 16 | }, 17 | 18 | initialize: function (options) { 19 | var self = this; 20 | this.project = this._project.bind(this); 21 | L.Util.setOptions(this, options); 22 | var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || 23 | window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; 24 | window.requestAnimationFrame = requestAnimationFrame; 25 | }, 26 | 27 | 28 | onAdd: function (map) { 29 | this._map = map; 30 | 31 | this._backCanvas = document.createElement('canvas'); 32 | this._canvas = document.createElement('canvas'); 33 | this._staticPane = map._createPane('leaflet-tile-pane', map._container); 34 | 35 | this._backCanvas.style.position = this._canvas.style.position = 'absolute'; 36 | this._staticPane.appendChild(this._backCanvas); 37 | this._staticPane.appendChild(this._canvas); 38 | 39 | this._backCtx = this._backCanvas.getContext('2d'); 40 | this._ctx = this._canvas.getContext('2d'); 41 | 42 | map.on({ 43 | 'viewreset': this._reset, 44 | 'move': this._update 45 | }, this); 46 | 47 | //if (map.options.zoomAnimation && L.Browser.any3d) { 48 | //map.on('zoomanim', this._animateZoom, this); 49 | //} 50 | // 51 | this._initTileLoader(); 52 | 53 | this._reset(); 54 | }, 55 | 56 | draw: function() { 57 | return this._reset(); 58 | }, 59 | 60 | onRemove: function (map) { 61 | map._container.removeChild(this._staticPane); 62 | map.off({ 63 | 'viewreset': this._reset, 64 | 'move': this._update 65 | }, this); 66 | 67 | //if (map.options.zoomAnimation) { 68 | //map.off('zoomanim', this._animateZoom, this); 69 | //} 70 | }, 71 | 72 | addTo: function (map) { 73 | map.addLayer(this); 74 | return this; 75 | }, 76 | 77 | setOpacity: function (opacity) { 78 | this.options.opacity = opacity; 79 | this._updateOpacity(); 80 | return this; 81 | }, 82 | 83 | // TODO remove bringToFront/bringToBack duplication from TileLayer/Path 84 | bringToFront: function () { 85 | return this; 86 | }, 87 | 88 | bringToBack: function () { 89 | return this; 90 | }, 91 | 92 | _reset: function () { 93 | var size = this._map.getSize() 94 | this._backCanvas.width = this._canvas.width = size.x; 95 | this._backCanvas.height = this._canvas.height = size.y; 96 | }, 97 | 98 | 99 | _project: function(x) { 100 | var point = this._map.latLngToLayerPoint(new L.LatLng(x[0], x[1])); 101 | return [point.x, point.y]; 102 | }, 103 | 104 | _updateOpacity: function () { }, 105 | 106 | _update: function() { 107 | //requestAnimationFrame(this._render); 108 | }, 109 | 110 | _render: function() { 111 | } 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /tornado/torque/js/leaflet_tileloader_mixin.js: -------------------------------------------------------------------------------- 1 | 2 | L.Mixin.TileLoader = { 3 | 4 | _initTileLoader: function() { 5 | this._tiles = {} 6 | this._tilesToLoad = 0; 7 | this._map.on({ 8 | 'moveend': this._updateTiles 9 | }, this); 10 | this._updateTiles(); 11 | }, 12 | 13 | _removeTileLoader: function() { 14 | map.off({ 15 | 'moveend': this._updateTiles 16 | }, this); 17 | //TODO: remove tiles 18 | }, 19 | 20 | _updateTiles: function () { 21 | 22 | if (!this._map) { return; } 23 | 24 | var bounds = this._map.getPixelBounds(), 25 | zoom = this._map.getZoom(), 26 | tileSize = this.options.tileSize; 27 | 28 | if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { 29 | return; 30 | } 31 | 32 | var nwTilePoint = new L.Point( 33 | Math.floor(bounds.min.x / tileSize), 34 | Math.floor(bounds.min.y / tileSize)), 35 | 36 | seTilePoint = new L.Point( 37 | Math.floor(bounds.max.x / tileSize), 38 | Math.floor(bounds.max.y / tileSize)), 39 | 40 | tileBounds = new L.Bounds(nwTilePoint, seTilePoint); 41 | 42 | this._addTilesFromCenterOut(tileBounds); 43 | this._removeOtherTiles(tileBounds); 44 | }, 45 | 46 | _removeOtherTiles: function (bounds) { 47 | var kArr, x, y, key; 48 | 49 | for (key in this._tiles) { 50 | if (this._tiles.hasOwnProperty(key)) { 51 | kArr = key.split(':'); 52 | x = parseInt(kArr[0], 10); 53 | y = parseInt(kArr[1], 10); 54 | 55 | // remove tile if it's out of bounds 56 | if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) { 57 | this._removeTile(key); 58 | } 59 | } 60 | } 61 | }, 62 | 63 | _removeTile: function (key) { 64 | this.fire('tileRemoved', this._tiles[key]); 65 | delete this._tiles[key]; 66 | }, 67 | 68 | _tileShouldBeLoaded: function (tilePoint) { 69 | return !((tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom) in this._tiles); 70 | }, 71 | 72 | _tileLoaded: function(tilePoint, tileData) { 73 | this._tilesToLoad--; 74 | this._tiles[tilePoint.x + ':' + tilePoint.y + ':' + tilePoint.zoom] = tileData; 75 | if(this._tilesToLoad === 0) { 76 | this.fire("tilesLoaded"); 77 | } 78 | }, 79 | 80 | _addTilesFromCenterOut: function (bounds) { 81 | var queue = [], 82 | center = bounds.getCenter(), 83 | zoom = this._map.getZoom(); 84 | 85 | var j, i, point; 86 | 87 | for (j = bounds.min.y; j <= bounds.max.y; j++) { 88 | for (i = bounds.min.x; i <= bounds.max.x; i++) { 89 | point = new L.Point(i, j); 90 | point.zoom = zoom; 91 | 92 | if (this._tileShouldBeLoaded(point)) { 93 | queue.push(point); 94 | } 95 | } 96 | } 97 | 98 | var tilesToLoad = queue.length; 99 | 100 | if (tilesToLoad === 0) { return; } 101 | 102 | // load tiles in order of their distance to center 103 | queue.sort(function (a, b) { 104 | return a.distanceTo(center) - b.distanceTo(center); 105 | }); 106 | 107 | this._tilesToLoad += tilesToLoad; 108 | 109 | for (i = 0; i < tilesToLoad; i++) { 110 | this.fire('tileAdded', queue[i]); 111 | } 112 | 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tornado/torque/js/map.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tornado/torque/js/particles.js: -------------------------------------------------------------------------------- 1 | 2 | var Entities = function(size, remove_callback) { 3 | this.x = new Float32Array(size); 4 | this.y = new Float32Array(size); 5 | this.life = new Float32Array(size); 6 | this.current_life = new Float32Array(size); 7 | this.remove = new Int32Array(size); 8 | this.type = new Int8Array(size); 9 | this.last = 0; 10 | this.size = size; 11 | this.sprites = [] 12 | 13 | this.sprites.push(this.pre_cache_sprites('rgba(255, 255, 255, 0.8)')) 14 | } 15 | 16 | Entities.prototype.pre_cache_sprites = function(color) { 17 | var sprites = [] 18 | for(var i = 0; i < 30; ++i) { 19 | var pixel_size = i*2 + 2; 20 | var canvas = document.createElement('canvas'); 21 | var ctx = canvas.getContext('2d'); 22 | ctx.width = canvas.width = pixel_size *2 ; 23 | ctx.height = canvas.height = pixel_size * 2 ; 24 | ctx.fillStyle = color;//'rgba(0, 255,255, 0.12)'; 25 | ctx.beginPath(); 26 | ctx.arc(pixel_size, pixel_size, pixel_size, 0, Math.PI*2, true, true); 27 | ctx.closePath(); 28 | ctx.fill(); 29 | sprites.push(canvas); 30 | } 31 | return sprites; 32 | } 33 | 34 | Entities.prototype.add = function(x, y, life, type) { 35 | if(this.last < this.size) { 36 | this.x[this.last] = x; 37 | this.y[this.last] = y; 38 | this.life[this.last] = Math.min(life, 29); 39 | this.current_life[this.last] = 0; 40 | this.type[this.last] = type; 41 | this.last++; 42 | } 43 | } 44 | 45 | Entities.prototype.dead = function(i) { 46 | return false; 47 | } 48 | 49 | Entities.prototype.render= function(ctx) { 50 | var s, t; 51 | for(var i = 0; i < this.last ; ++i) { 52 | s = (this.current_life[i])>>0; 53 | t = this.type[i]; 54 | //ctx.arc(this.x[i], this.y[i] ,3*this.life[i], 0, 2*Math.PI, true, true); 55 | ctx.drawImage(this.sprites[t][s], (this.x[i] - s*2)>>0, (this.y[i] - s*2)>>0); 56 | } 57 | } 58 | 59 | Entities.prototype.update = function(dt) { 60 | var len = this.last; 61 | var removed = 0; 62 | var _remove = this.remove; 63 | 64 | for(var i = len - 1; i >= 0; --i) { 65 | //var c = (this.life[i] -= this.life[i]*0.15); 66 | var diff = this.life[i] - this.current_life[i]; 67 | this.current_life[i] += diff*dt*3 68 | if(diff <= 0.05) { 69 | _remove[removed++] = i; 70 | } 71 | } 72 | 73 | for(var ri = 0; ri < removed; ++ri) { 74 | var r = _remove[ri]; 75 | var last = this.last - 1; 76 | // move last to the removed one and remove it 77 | this.x[r] = this.x[last]; 78 | this.y[r] = this.y[last]; 79 | this.life[r] = this.life[last]; 80 | this.current_life[r] = this.current_life[last] 81 | this.type[r] = this.type[last]; 82 | 83 | this.last--; 84 | } 85 | }; 86 | 87 | 88 | -------------------------------------------------------------------------------- /tornado/torque/js/sprites.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * generate sprites in small canvas to speedup rendering 4 | */ 5 | 6 | var Sprites = { 7 | 8 | render_to_canvas: function(fn, w, h) { 9 | var canvas = document.createElement('canvas'); 10 | var ctx = canvas.getContext('2d'); 11 | canvas.width = w; 12 | canvas.height = h 13 | fn(ctx, w, h); 14 | return canvas; 15 | }, 16 | 17 | draw_circle_glow: function(ctx, pixel_size, color, exp) { 18 | var p; 19 | var I = ctx.getImageData(0, 0, pixel_size, pixel_size); 20 | var pixels = I.data; 21 | var cx = pixel_size >> 1; 22 | var cy = pixel_size >> 1; 23 | for(var x = 0; x < pixel_size; ++x) { 24 | for(var y = 0; y < pixel_size; ++y) { 25 | p = (y*pixel_size + x) * 4; 26 | var dx = x - cx; 27 | var dy = y - cy; 28 | var r = Math.sqrt(dx * dx + dy * dy)/pixel_size; 29 | pixels[p + 0] = color[0]; 30 | pixels[p + 1] = color[1]; 31 | pixels[p + 2] = color[2]; 32 | pixels[p + 3] = color[3]*Math.cos(r*Math.PI) 33 | //var a = Math.cos(r*Math.PI) 34 | //pixels[p + 3] = color[3]*a*a*a*a*a; 35 | pixels[p + 3] = color[3]*Math.exp(-r*exp) 36 | } 37 | } 38 | ctx.putImageData(I, 0, 0) 39 | }, 40 | 41 | circle: function(ctx, size, color) { 42 | var pixel_size = size >> 1; 43 | var tau = Math.PI * 2; 44 | ctx.fillStyle = color 45 | ctx.beginPath(); 46 | ctx.arc(pixel_size, pixel_size, pixel_size, 0, tau, true, true); 47 | ctx.closePath(); 48 | ctx.fill(); 49 | } 50 | 51 | }; // Sprites 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /tornado/torque/js/street_density.js: -------------------------------------------------------------------------------- 1 | 2 | var StreetLayerDensity = L.CanvasLayer.extend({ 3 | initialize: function() { 4 | L.CanvasLayer.prototype.initialize.call(this); 5 | this.getDensityData(); 6 | this.entities = new Entities(13000); 7 | this.time = 0; 8 | this.density_data = null; 9 | }, 10 | 11 | getDensityData: function() { 12 | var self = this; 13 | $.getJSON("twomonths_london_14_96_count.json", function(data) { 14 | overall_activity = []; 15 | var max_activity = 0; 16 | var simplify = d3.simplify(); 17 | for (var i = 0, len = data.length; i < len; ++i) { 18 | var r = data[i]; 19 | var coords = r.vertex; 20 | var activity = r.activity; 21 | var grouped_activity = [] 22 | for(var a = 0; a < activity[0].length; ++a) { 23 | grouped_activity[a] = 0.0; 24 | for(var aa = 0; aa < activity.length; ++aa) { 25 | var v = activity[aa][a]; 26 | max_activity = Math.max(max_activity, v); 27 | grouped_activity[a] += v; 28 | } 29 | overall_activity[a] = overall_activity[a] || 0; 30 | overall_activity[a] += grouped_activity[a]; 31 | } 32 | var coords = r.vertex; 33 | r.activity = grouped_activity; 34 | //r.vertex = simplify(coords); 35 | } 36 | //normalize activity 37 | var m = 0; 38 | for(var i = 0; i < overall_activity.length; ++i) { 39 | m = Math.max(overall_activity[i], m); 40 | } 41 | for(var i = 0; i < overall_activity.length; ++i) { 42 | overall_activity[i] /= m; 43 | } 44 | 45 | console.log("overall_activity", overall_activity); 46 | self.density_data = data; 47 | self.density_data.overall_activity = overall_activity; 48 | }); 49 | }, 50 | 51 | onAdd: function (map) { 52 | L.CanvasLayer.prototype.onAdd.call(this, map); 53 | var origin = this._map._getNewTopLeftPoint(this._map.getCenter(), this._map.getZoom()); 54 | this._ctx.translate(-origin.x, -origin.y); 55 | }, 56 | 57 | set_time: function(t) { 58 | this.time = t; 59 | }, 60 | 61 | _render2: function(delta) { 62 | this._canvas.width = this._canvas.width; 63 | var w2 = this._canvas.width/2; 64 | var h2 = this._canvas.height/2; 65 | var origin = this._map._getNewTopLeftPoint(this._map.getCenter(), this._map.getZoom()); 66 | this._ctx.translate(-origin.x, -origin.y); 67 | if(!this.density_data) return; 68 | for(var i = 0; i < this.density_data.length; ++i) { 69 | var vertex = this.density_data[i].vertex; 70 | var zoom = this._map.getZoom() 71 | for(var v = 0; v < vertex.length; ++v) { 72 | var p0 = this._map.project(vertex[v], zoom); 73 | this._ctx.fillRect(p0.x, p0.y, 2, 2); 74 | } 75 | } 76 | }, 77 | 78 | _render: function(delta) { 79 | this._canvas.width = this._canvas.width; 80 | var w2 = this._canvas.width/2; 81 | var h2 = this._canvas.height/2; 82 | var origin = this._map._getNewTopLeftPoint(this._map.getCenter(), this._map.getZoom()); 83 | this._ctx.translate(-origin.x, -origin.y); 84 | //this._ctx.fillStyle = 'rgba(52, 111, 190, 0.01)';//rgba(0, 0, 0, 0.03)';//'rgba(255, 255,255, 0.3)'; 85 | //this._ctx.globalCompositeOperation = 'source-over'; 86 | //this._ctx.fillRect(origin.x, origin.y, this._canvas.width, this._canvas.height); 87 | this._ctx.fillStyle = 'rgba(255, 255, 255, 0.2);' 88 | if(this.time >50) { 89 | } 90 | this._ctx.fillStyle = 'rgba(30, 60, 100, 0.7)'; 91 | //this._ctx.fillStyle= 'rgba(200, 35, 0, 0.2)'; 92 | //this._ctx.strokeStyle= 'rgba(200, 35, 0, 0.2)'; 93 | //this._ctx.globalCompositeOperation = 'lighter'; 94 | this.entities.update(delta); 95 | this.entities.render(this._ctx); 96 | 97 | 98 | if(!this.density_data) return; 99 | var O_KE_ASE = this.density_data.overall_activity[this.time]*200; 100 | O_KE_ASE = O_KE_ASE>>0; 101 | var tries = O_KE_ASE*5; 102 | while(O_KE_ASE && tries--) { 103 | var street = this.density_data[(Math.random()*this.density_data.length)>>0]; 104 | var activity = street.activity[this.time]; 105 | if(activity > 0 ) { 106 | activity /= 3; 107 | while(activity-- > 0) { 108 | var vertex = street.vertex; 109 | var p = (Math.random()*(vertex.length - 1))>>0; 110 | var zoom = this._map.getZoom() 111 | var p0 = this._map.project(vertex[p], zoom); 112 | var p1 = this._map.project(vertex[p + 1], zoom); 113 | var dx = p1.x - p0.x; 114 | var dy = p1.y - p0.y; 115 | this.entities.add( 116 | p0.x + rand(), 117 | p0.y + rand(), 118 | 6*dx + rand(), 119 | 6*dy + rand(), 120 | 0.1 + 10*Math.random(), 121 | 0 122 | //activity/30 123 | ); 124 | O_KE_ASE--; 125 | } 126 | } 127 | } 128 | 129 | this._backCtx.globalAlpha = 1; 130 | this._backCtx.globalCompositeOperation = 'source-over'; 131 | //this._backCtx.fillStyle = 'rgba(52, 111, 190, 0.1)'; 132 | //this._backCtx.fillRect(0, 0, this._backCanvas.width, this._backCanvas.height); 133 | //this._backCtx.fillRect(0, 0, this._backCanvas.width, this._backCanvas.height); 134 | this._backCtx.globalAlpha = 0.01; 135 | this._backCtx.globalCompositeOperation = 'lighter'; 136 | this._backCtx.drawImage(this._canvas, 0, 0); 137 | 138 | }, 139 | }); 140 | -------------------------------------------------------------------------------- /tornado/torque/js/torque-hm-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Sun Ning 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | onmessage = function(e){ 23 | calc(e.data); 24 | } 25 | 26 | function calc(params) { 27 | value = params.value || {}; 28 | degree = params.degree || 1; 29 | 30 | for(var pos in params.data){ 31 | var data = params.data[pos]; 32 | var radius = Math.floor(Math.pow((data / params.step), 1/degree)); 33 | 34 | var x = Math.floor(pos%params.width); 35 | var y = Math.floor(pos/params.width); 36 | 37 | // calculate point x.y 38 | for(var scanx=x-radius; scanxparams.width){ 41 | continue; 42 | } 43 | for(var scany=y-radius; scanyparams.height){ 46 | continue; 47 | } 48 | 49 | var dist = Math.sqrt(Math.pow((scanx-x), 2)+Math.pow((scany-y), 2)); 50 | if(dist > radius){ 51 | continue; 52 | } else { 53 | var v = data - params.step * Math.pow(dist, degree); 54 | 55 | var id = scanx+scany*params.width ; 56 | 57 | if(value[id]){ 58 | value[id] = value[id] + v; 59 | } else { 60 | value[id] = v; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | postMessage({'value': value}); 67 | } 68 | -------------------------------------------------------------------------------- /tornado/torque/js/util.js: -------------------------------------------------------------------------------- 1 | 2 | function rand() { 3 | return 2.0*(Math.random() - 0.5) 4 | } 5 | String.prototype.format = (function (i, safe, arg) { 6 | function format() { 7 | var str = this, 8 | len = arguments.length + 1; 9 | 10 | for (i = 0; i < len; arg = arguments[i++]) { 11 | safe = typeof arg === 'object' ? JSON.stringify(arg) : arg; 12 | str = str.replace(RegExp('\\{' + (i - 1) + '\\}', 'g'), safe); 13 | } 14 | return str; 15 | } 16 | 17 | //format.native = String.prototype.format; 18 | return format; 19 | })(); 20 | 21 | var d3 = {} 22 | d3.simplify = function() { 23 | 24 | function simplify(coordinates) { 25 | 26 | var heap = minHeap(), 27 | maxArea = 0, 28 | triangle; 29 | 30 | coordinates = coordinates.map(function(polygon) { 31 | return polygon.map(function(lineString) { 32 | var points = lineString; 33 | triangles = []; 34 | 35 | for (var i = 1, n = lineString.length - 1; i < n; ++i) { 36 | triangle = points.slice(i - 1, i + 2); 37 | if (triangle[1][2] = area(triangle)) { 38 | triangles.push(triangle); 39 | heap.push(triangle); 40 | } 41 | } 42 | 43 | for (var i = 0, n = triangles.length; i < n; ++i) { 44 | triangle = triangles[i]; 45 | triangle.previous = triangles[i - 1]; 46 | triangle.next = triangles[i + 1]; 47 | } 48 | 49 | return points; 50 | }); 51 | }); 52 | 53 | while (triangle = heap.pop()) { 54 | 55 | // If the area of the current point is less than that of the previous point 56 | // to be eliminated, use the latter’s area instead. This ensures that the 57 | // current point cannot be eliminated without eliminating previously- 58 | // eliminated points. 59 | if (triangle[1][2] < maxArea) triangle[1][2] = maxArea; 60 | else maxArea = triangle[1][2]; 61 | 62 | if (triangle.previous) { 63 | triangle.previous.next = triangle.next; 64 | triangle.previous[2] = triangle[2]; 65 | update(triangle.previous); 66 | } else { 67 | triangle[0][2] = triangle[1][2]; 68 | } 69 | 70 | if (triangle.next) { 71 | triangle.next.previous = triangle.previous; 72 | triangle.next[0] = triangle[0]; 73 | update(triangle.next); 74 | } else { 75 | triangle[2][2] = triangle[1][2]; 76 | } 77 | } 78 | 79 | function update(triangle) { 80 | heap.remove(triangle); 81 | triangle[1][2] = area(triangle); 82 | heap.push(triangle); 83 | } 84 | 85 | return coordinates; 86 | } 87 | 88 | return simplify; 89 | }; 90 | 91 | function compare(a, b) { 92 | return a[1][2] - b[1][2]; 93 | } 94 | 95 | function area(t) { 96 | return Math.abs((t[0][0] - t[2][0]) * (t[1][1] - t[0][1]) - (t[0][0] - t[1][0]) * (t[2][1] - t[0][1])); 97 | } 98 | 99 | function minHeap() { 100 | var heap = {}, 101 | array = []; 102 | 103 | heap.push = function() { 104 | for (var i = 0, n = arguments.length; i < n; ++i) { 105 | var object = arguments[i]; 106 | up(object.index = array.push(object) - 1); 107 | } 108 | return array.length; 109 | }; 110 | 111 | heap.pop = function() { 112 | var removed = array[0], 113 | object = array.pop(); 114 | if (array.length) { 115 | array[object.index = 0] = object; 116 | down(0); 117 | } 118 | return removed; 119 | }; 120 | 121 | heap.remove = function(removed) { 122 | var i = removed.index, 123 | object = array.pop(); 124 | if (i !== array.length) { 125 | array[object.index = i] = object; 126 | (compare(object, removed) < 0 ? up : down)(i); 127 | } 128 | return i; 129 | }; 130 | 131 | function up(i) { 132 | var object = array[i]; 133 | while (i > 0) { 134 | var up = ((i + 1) >> 1) - 1, 135 | parent = array[up]; 136 | if (compare(object, parent) >= 0) break; 137 | array[parent.index = i] = parent; 138 | array[object.index = i = up] = object; 139 | } 140 | } 141 | 142 | function down(i) { 143 | var object = array[i]; 144 | while (true) { 145 | var right = (i + 1) << 1, 146 | left = right - 1, 147 | down = i, 148 | child = array[down]; 149 | if (left < array.length && compare(array[left], child) < 0) child = array[down = left]; 150 | if (right < array.length && compare(array[right], child) < 0) child = array[down = right]; 151 | if (down === i) break; 152 | array[child.index = i] = child; 153 | array[object.index = i = down] = object; 154 | } 155 | } 156 | 157 | return heap; 158 | } 159 | -------------------------------------------------------------------------------- /tornado/torque/scss/style.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewxhill/cartodb-examples/800ff4c519611e3bdac58bb0b3f471ea85506c61/tornado/torque/scss/style.scss -------------------------------------------------------------------------------- /videomap/html5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 38 | 39 | 40 | 43 | 44 | 45 | 46 |
47 | 48 | 49 |
50 | 51 | 53 |
54 |
55 | 62 |
63 | 64 | 65 | 66 | 67 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /videomap/inset/CanvasOverlay.js: -------------------------------------------------------------------------------- 1 | L.CanvasOverlay = L.Class.extend({ 2 | includes: L.Mixin.Events, 3 | 4 | options: { 5 | opacity: 1 6 | }, 7 | 8 | initialize: function (topLeft, size, options) { // (String, LatLngBounds, Object) 9 | // this._canvas = canvas; 10 | // this._ctx = canvas.getContext('2d'); 11 | // this._bounds = L.latLngBounds(bounds); 12 | this._inView = true; 13 | this._topLeft = topLeft; 14 | this._size = size; 15 | this._origSize = size; 16 | L.setOptions(this, options); 17 | 18 | }, 19 | getContext: function(){ 20 | return this._context; 21 | }, 22 | getCanvas: function(){ 23 | return this._canvas; 24 | }, 25 | onAdd: function (map) { 26 | this._map = map; 27 | 28 | if (!this._canvas) { 29 | this._initImage(); 30 | } 31 | 32 | map._panes.overlayPane.appendChild(this._canvas); 33 | 34 | map.on('viewreset', this._reset, this); 35 | 36 | if (map.options.zoomAnimation && L.Browser.any3d) { 37 | map.on('zoomanim', this._animateZoom, this); 38 | } 39 | 40 | this._reset(); 41 | }, 42 | 43 | onRemove: function (map) { 44 | map.getPanes().overlayPane.removeChild(this._canvas); 45 | 46 | map.off('viewreset', this._reset, this); 47 | 48 | if (map.options.zoomAnimation) { 49 | map.off('zoomanim', this._animateZoom, this); 50 | } 51 | }, 52 | 53 | addTo: function (map) { 54 | map.addLayer(this); 55 | return this; 56 | }, 57 | 58 | setOpacity: function (opacity) { 59 | this.options.opacity = opacity; 60 | this._updateOpacity(); 61 | return this; 62 | }, 63 | 64 | // TODO remove bringToFront/bringToBack duplication from TileLayer/Path 65 | bringToFront: function () { 66 | if (this._canvas) { 67 | this._map._panes.overlayPane.appendChild(this._canvas); 68 | } 69 | return this; 70 | }, 71 | 72 | bringToBack: function () { 73 | var pane = this._map._panes.overlayPane; 74 | if (this._canvas) { 75 | pane.insertBefore(this._canvas, pane.firstChild); 76 | } 77 | return this; 78 | }, 79 | 80 | _initImage: function () { 81 | this._canvas = L.DomUtil.create('canvas', 'leaflet-image-layer'); 82 | this._width = this._canvas.width = this._size.x; 83 | this._height = this._canvas.height = this._size.y; 84 | this._context = this._canvas.getContext('2d'); 85 | 86 | if (this._map.options.zoomAnimation && L.Browser.any3d) { 87 | L.DomUtil.addClass(this._canvas, 'leaflet-zoom-animated'); 88 | } else { 89 | L.DomUtil.addClass(this._canvas, 'leaflet-zoom-hide'); 90 | } 91 | 92 | this._updateOpacity(); 93 | 94 | //TODO createImage util method to remove duplication 95 | L.extend(this._canvas, { 96 | galleryimg: 'no', 97 | onselectstart: L.Util.falseFn, 98 | onmousemove: L.Util.falseFn, 99 | onload: L.bind(this._onImageLoad, this), 100 | // src: this._canvas.toDataURL() 101 | }); 102 | }, 103 | _outOfFocus: function() { 104 | if (this._inView){ 105 | this._returnZoom = this._map.getZoom() - 1; 106 | this._oldOpacity = this.options.opacity; 107 | this.options.opacity = 0; 108 | this._updateOpacity(); 109 | this._inView = false; 110 | } 111 | }, 112 | _inFocus: function() { 113 | if (this._returnZoom >= this._map.getZoom()) { 114 | this.options.opacity = this._oldOpacity; 115 | console.log(this._oldOpacity) 116 | this._updateOpacity(); 117 | this._inView = true; 118 | } 119 | }, 120 | _animateZoom: function (e) { 121 | var map = this._map, 122 | image = this._canvas, 123 | scale = map.getZoomScale(e.zoom), 124 | topLeft = map._latLngToNewLayerPoint(this._topLeft, e.zoom, e.center); 125 | 126 | 127 | var size = {x: scale*this._canvas.width, y: scale*this._canvas.height}; 128 | 129 | if (size.x < 10) {this._outOfFocus(); return;} 130 | if (size.x < 10) {this._outOfFocus(); return;} 131 | if (!this._inView){ 132 | this._inFocus(); 133 | } else { 134 | console.log('animate') 135 | 136 | this._size = size; 137 | origin = topLeft._add({x: this._size.x * (1 / 2) * (1 - 1/scale), y: this._size.y * (1 / 2) * (1 - 1/scale)}) 138 | 139 | image.style[L.DomUtil.TRANSFORM] = 140 | L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; 141 | 142 | this._width = this._canvas.width = this._size.x; 143 | this._height = this._canvas.height = this._size.y ; 144 | } 145 | }, 146 | 147 | _reset: function (size) { 148 | if(this._inView){ 149 | var image = this._canvas, 150 | topLeft = this._map.latLngToLayerPoint(this._topLeft) 151 | size = this._size; 152 | 153 | L.DomUtil.setPosition(image, topLeft); 154 | 155 | image.style.width = size.x + 'px'; 156 | image.style.height = size.y + 'px'; 157 | console.log('reset') 158 | } 159 | }, 160 | 161 | _onImageLoad: function () { 162 | this.fire('load'); 163 | }, 164 | 165 | _updateOpacity: function () { 166 | L.DomUtil.setOpacity(this._canvas, this.options.opacity); 167 | } 168 | }); 169 | 170 | L.canvasOverlay = function (url, bounds, options) { 171 | return new L.CanvasOverlay(url, bounds, options); 172 | }; -------------------------------------------------------------------------------- /videomap/inset/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet Video Layer (Mwahaha) 5 | 6 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 133 | 143 | 144 | -------------------------------------------------------------------------------- /videomap/video-inset/VideoOverlay.js: -------------------------------------------------------------------------------- 1 | L.VideoOverlay = L.Class.extend({ 2 | includes: L.Mixin.Events, 3 | 4 | options: { 5 | opacity: 1 6 | }, 7 | 8 | initialize: function (topLeft, size, options) { // (String, LatLngBounds, Object) 9 | // this._video = video; 10 | // this._ctx = video.getContext('2d'); 11 | // this._bounds = L.latLngBounds(bounds); 12 | this._inView = true; 13 | this._topLeft = topLeft; 14 | this._size = size; 15 | this._src = options.src; 16 | this._origSize = size; 17 | L.setOptions(this, options); 18 | 19 | }, 20 | getContext: function(){ 21 | return this._context; 22 | }, 23 | getVideo: function(){ 24 | return this._video; 25 | }, 26 | onAdd: function (map) { 27 | this._map = map; 28 | 29 | if (!this._video) { 30 | this._initImage(); 31 | } 32 | 33 | map._panes.overlayPane.appendChild(this._video); 34 | 35 | map.on('viewreset', this._reset, this); 36 | 37 | if (map.options.zoomAnimation && L.Browser.any3d) { 38 | map.on('zoomanim', this._animateZoom, this); 39 | } 40 | 41 | this._reset(); 42 | }, 43 | 44 | onRemove: function (map) { 45 | map.getPanes().overlayPane.removeChild(this._video); 46 | 47 | map.off('viewreset', this._reset, this); 48 | 49 | if (map.options.zoomAnimation) { 50 | map.off('zoomanim', this._animateZoom, this); 51 | } 52 | }, 53 | 54 | addTo: function (map) { 55 | map.addLayer(this); 56 | return this; 57 | }, 58 | 59 | setOpacity: function (opacity) { 60 | this.options.opacity = opacity; 61 | this._updateOpacity(); 62 | return this; 63 | }, 64 | 65 | // TODO remove bringToFront/bringToBack duplication from TileLayer/Path 66 | bringToFront: function () { 67 | if (this._video) { 68 | this._map._panes.overlayPane.appendChild(this._video); 69 | } 70 | return this; 71 | }, 72 | 73 | bringToBack: function () { 74 | var pane = this._map._panes.overlayPane; 75 | if (this._video) { 76 | pane.insertBefore(this._video, pane.firstChild); 77 | } 78 | return this; 79 | }, 80 | 81 | _initImage: function () { 82 | this._video = L.DomUtil.create('iframe', 'leaflet-image-layer'); 83 | this._video.src = this._src; 84 | this._width = this._video.width = this._size.x; 85 | this._height = this._video.height = this._size.y; 86 | 87 | if (this._map.options.zoomAnimation && L.Browser.any3d) { 88 | L.DomUtil.addClass(this._video, 'leaflet-zoom-animated'); 89 | } else { 90 | L.DomUtil.addClass(this._video, 'leaflet-zoom-hide'); 91 | } 92 | 93 | this._updateOpacity(); 94 | 95 | //TODO createImage util method to remove duplication 96 | L.extend(this._video, { 97 | galleryimg: 'no', 98 | onselectstart: L.Util.falseFn, 99 | onmousemove: L.Util.falseFn, 100 | onload: L.bind(this._onImageLoad, this), 101 | // src: this._video.toDataURL() 102 | }); 103 | }, 104 | _outOfFocus: function() { 105 | if (this._inView){ 106 | this._returnZoom = this._map.getZoom() - 1; 107 | this._oldOpacity = this.options.opacity; 108 | this.options.opacity = 0; 109 | this._updateOpacity(); 110 | this._inView = false; 111 | } 112 | }, 113 | _inFocus: function() { 114 | if (this._returnZoom >= this._map.getZoom()) { 115 | this.options.opacity = this._oldOpacity; 116 | this._updateOpacity(); 117 | this._inView = true; 118 | } 119 | }, 120 | _animateZoom: function (e) { 121 | var map = this._map, 122 | image = this._video, 123 | scale = map.getZoomScale(e.zoom), 124 | topLeft = map._latLngToNewLayerPoint(this._topLeft, e.zoom, e.center); 125 | 126 | 127 | var size = {x: scale*this._video.width, y: scale*this._video.height}; 128 | 129 | if (size.x < 10) {this._outOfFocus(); return;} 130 | if (size.x < 10) {this._outOfFocus(); return;} 131 | if (!this._inView){ 132 | this._inFocus(); 133 | } else { 134 | this._size = size; 135 | origin = topLeft._add({x: this._size.x * (1 / 2) * (1 - 1/scale), y: this._size.y * (1 / 2) * (1 - 1/scale)}) 136 | 137 | image.style[L.DomUtil.TRANSFORM] = 138 | L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; 139 | 140 | this._width = this._video.width = this._size.x; 141 | this._height = this._video.height = this._size.y ; 142 | } 143 | }, 144 | 145 | _reset: function (size) { 146 | if(this._inView){ 147 | var image = this._video, 148 | topLeft = this._map.latLngToLayerPoint(this._topLeft) 149 | size = this._size; 150 | 151 | L.DomUtil.setPosition(image, topLeft); 152 | 153 | image.style.width = size.x + 'px'; 154 | image.style.height = size.y + 'px'; 155 | console.log('reset') 156 | } 157 | }, 158 | 159 | _onImageLoad: function () { 160 | this.fire('load'); 161 | }, 162 | 163 | _updateOpacity: function () { 164 | L.DomUtil.setOpacity(this._video, this.options.opacity); 165 | } 166 | }); 167 | 168 | L.videoOverlay = function (url, bounds, options) { 169 | return new L.VideoOverlay(url, bounds, options); 170 | }; -------------------------------------------------------------------------------- /videomap/video-inset/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Leaflet Video Layer (Mwahaha) 5 | 6 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 62 | 63 | -------------------------------------------------------------------------------- /videomap/vimeo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 38 | 39 | 40 | 43 | 44 | 45 | 46 |
47 | 48 | 49 |
50 | 51 |
52 |
53 | 60 |
61 | 62 | 63 | 64 | 65 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /writable/function.sql: -------------------------------------------------------------------------------- 1 | -- Create a function withn the security set to Definer so that it can insert 2 | CREATE OR REPLACE FUNCTION AXH_NewMinneapolis(integer, geometry) RETURNS integer 3 | AS 'INSERT INTO minneapolis (parent_id, the_geom) (SELECT id, geom FROM (SELECT $1 id,$2 geom) a WHERE ST_NPoints(geom) < 100) RETURNING cartodb_id;' 4 | LANGUAGE SQL 5 | SECURITY DEFINER 6 | RETURNS NULL ON NULL INPUT; 7 | --Grant access to the public user 8 | GRANT EXECUTE ON FUNCTION AXH_NewMinneapolis(integer, geometry) TO publicuser; 9 | -------------------------------------------------------------------------------- /writable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 |
57 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 144 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /writable/polygon-size.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 |
68 |
Click to edit this polygon
69 | 72 | 73 | 74 | 75 | 76 | 119 | 120 | 121 | --------------------------------------------------------------------------------