A combination of the map zooming and dynamic simplification demonstrations: as the map zooms in and out, the simplification area threshold is adjusted so that it is always appropriate to the current scale. Thus, the map looks good and renders quickly at all points during the animation.
An additional rendering optimization is that points outside the current viewport are much more heavily simplified than points inside the viewport. This is, in effect, a cheap approximation of viewport clipping.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="d3.min.js"></script>
<script src="topojson.min.js"></script>
<script>
var width = 960,
height = 500;
var chesapeake = [-75.959, 38.250];
var scale,
translate,
visibleArea, // minimum area threshold for points inside viewport
invisibleArea; // minimum area threshold for points outside viewport
var simplify = d3.geo.transform({
point: function(x, y, z) {
if (z < visibleArea) return;
x = x * scale + translate[0];
y = y * scale + translate[1];
if (x >= 0 && x <= width && y >= 0 && y <= height || z >= invisibleArea) this.stream.point(x, y);
}
});
var zoom = d3.behavior.zoom()
.size([width, height])
.on("zoom", zoomed);
// This projection is baked into the TopoJSON file,
// but is used here to compute the desired zoom translate.
var projection = d3.geo.equirectangular()
.translate([0, 0])
.scale(350)
.precision(0);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(simplify)
.context(context);
d3.json("https://rawgithub.com/wboykinm/ophz/master/ophz-b/ophz-b-wgs84/ophz-b-wgs84.topojson", function(error, ophz) {
canvas
.datum(topojson.mesh(topojson.presimplify(ophz)))
.call(zoomTo(chesapeake, 0.05).event)
.transition()
.duration(5000)
.each(jump);
});
function zoomTo(location, scale) {
var point = projection(location);
return zoom
.translate([width / 2 - point[0] * scale, height / 2 - point[1] * scale])
.scale(scale);
}
function zoomed(d) {
translate = zoom.translate();
scale = zoom.scale();
visibleArea = 1 / scale / scale;
invisibleArea = 200 * visibleArea;
context.clearRect(0, 0, width, height);
context.beginPath();
path(d);
context.stroke();
}
function jump() {
var t = d3.select(this);
(function repeat() {
t = t.transition()
.call(zoomTo(chesapeake, 1.25).event)
.transition()
.call(zoomTo(chesapeake, 0.05).event)
.each("end", repeat);
})();
}
</script>
topojson=function(){function e(e,t){function n(t){var n=e.arcs[t],r=n[0],a=[0,0];return n.forEach(function(e){a[0]+=e[0],a[1]+=e[1]}),[r,a]}var r={},a={};t.forEach(function(e){var t,o,i=n(e),u=i[0],c=i[1];if(t=a[u])if(delete a[t.end],t.push(e),t.end=c,o=r[c]){delete r[o.start];var s=o===t?t:t.concat(o);r[s.start=t.start]=a[s.end=o.end]=s}else if(o=a[c]){delete r[o.start],delete a[o.end];var s=t.concat(o.map(function(e){return~e}).reverse());r[s.start=t.start]=a[s.end=o.start]=s}else r[t.start]=a[t.end]=t;else if(t=r[c])if(delete r[t.start],t.unshift(e),t.start=u,o=a[u]){delete a[o.end];var f=o===t?t:o.concat(t);r[f.start=o.start]=a[f.end=t.end]=f}else if(o=r[u]){delete r[o.start],delete a[o.end];var f=o.map(function(e){return~e}).reverse().concat(t);r[f.start=o.end]=a[f.end=t.end]=f}else r[t.start]=a[t.end]=t;else if(t=r[u])if(delete r[t.start],t.unshift(~e),t.start=c,o=a[c]){delete a[o.end];var f=o===t?t:o.concat(t);r[f.start=o.start]=a[f.end=t.end]=f}else if(o=r[c]){delete r[o.start],delete a[o.end];var f=o.map(function(e){return~e}).reverse().concat(t);r[f.start=o.end]=a[f.end=t.end]=f}else r[t.start]=a[t.end]=t;else if(t=a[c])if(delete a[t.end],t.push(~e),t.end=u,o=a[u]){delete r[o.start];var s=o===t?t:t.concat(o);r[s.start=t.start]=a[s.end=o.end]=s}else if(o=r[u]){delete r[o.start],delete a[o.end];var s=t.concat(o.map(function(e){return~e}).reverse());r[s.start=t.start]=a[s.end=o.start]=s}else r[t.start]=a[t.end]=t;else t=[e],r[t.start=u]=a[t.end=c]=t});var o=[];for(var i in a)o.push(a[i]);return o}function t(t,n,r){function o(e){0>e&&(e=~e),(l[e]||(l[e]=[])).push(f)}function i(e){e.forEach(o)}function u(e){e.forEach(i)}function c(e){"GeometryCollection"===e.type?e.geometries.forEach(c):e.type in d&&(f=e,d[e.type](e.arcs))}var s=[];if(arguments.length>1){var f,l=[],d={LineString:i,MultiLineString:u,Polygon:u,MultiPolygon:function(e){e.forEach(u)}};c(n),l.forEach(arguments.length<3?function(e,t){s.push(t)}:function(e,t){r(e[0],e[e.length-1])&&s.push(t)})}else for(var p=0,v=t.arcs.length;v>p;++p)s.push(p);return a(t,{type:"MultiLineString",arcs:e(t,s)})}function n(e,t){return"GeometryCollection"===t.type?{type:"FeatureCollection",features:t.geometries.map(function(t){return r(e,t)})}:r(e,t)}function r(e,t){var n={type:"Feature",id:t.id,properties:t.properties||{},geometry:a(e,t)};return null==t.id&&delete n.id,n}function a(e,t){function n(e,t){t.length&&t.pop();for(var n,r=v[0>e?~e:e],a=0,i=r.length,u=0,c=0;i>a;++a)t.push(n=r[a].slice()),n[0]=(u+=n[0])*f+d,n[1]=(c+=n[1])*l+p;0>e&&o(t,i)}function r(e){return e=e.slice(),e[0]=e[0]*f+d,e[1]=e[1]*l+p,e}function a(e){for(var t=[],r=0,a=e.length;a>r;++r)n(e[r],t);return t.length<2&&t.push(t[0].slice()),t}function i(e){for(var t=a(e);t.length<4;)t.push(t[0].slice());return t}function u(e){return e.map(i)}function c(e){var t=e.type;return"GeometryCollection"===t?{type:t,geometries:e.geometries.map(c)}:t in h?{type:t,coordinates:h[t](e)}:null}var s=e.transform,f=s.scale[0],l=s.scale[1],d=s.translate[0],p=s.translate[1],v=e.arcs,h={Point:function(e){return r(e.coordinates)},MultiPoint:function(e){return e.coordinates.map(r)},LineString:function(e){return a(e.arcs)},MultiLineString:function(e){return e.arcs.map(a)},Polygon:function(e){return u(e.arcs)},MultiPolygon:function(e){return e.arcs.map(u)}};return c(t)}function o(e,t){for(var n,r=e.length,a=r-t;a<--r;)n=e[a],e[a++]=e[r],e[r]=n}function i(e,t){for(var n=0,r=e.length;r>n;){var a=n+r>>>1;e[a]<t?n=a+1:r=a}return n}function u(e){function t(e,t){e.forEach(function(e){0>e&&(e=~e);var n=a[e];n?n.push(t):a[e]=[t]})}function n(e,n){e.forEach(function(e){t(e,n)})}function r(e,t){"GeometryCollection"===e.type?e.geometries.forEach(function(e){r(e,t)}):e.type in u&&u[e.type](e.arcs,t)}var a={},o=e.map(function(){return[]}),u={LineString:t,MultiLineString:n,Polygon:n,MultiPolygon:function(e,t){e.forEach(function(e){n(e,t)})}};e.forEach(r);for(var c in a)for(var s=a[c],f=s.length,l=0;f>l;++l)for(var d=l+1;f>d;++d){var p,v=s[l],h=s[d];(p=o[v])[c=i(p,h)]!==h&&p.splice(c,0,h),(p=o[h])[c=i(p,v)]!==v&&p.splice(c,0,v)}return o}function c(e,t){function n(e){a.remove(e),e[1][2]=t(e),a.push(e)}var r,a=l(f),o=0;for(t||(t=s),e.arcs.forEach(function(n){var o=[];n.forEach(d(e.transform));for(var i=1,u=n.length-1;u>i;++i)r=n.slice(i-1,i+2),r[1][2]=t(r),o.push(r),a.push(r);n[0][2]=n[u][2]=1/0;for(var i=0,u=o.length;u>i;++i)r=o[i],r.previous=o[i-1],r.next=o[i+1]});r=a.pop();){var i=r.previous,u=r.next;r[1][2]<o?r[1][2]=o:o=r[1][2],i&&(i.next=u,i[2]=r[2],n(i)),u&&(u.previous=i,u[0]=r[0],n(u))}return e.arcs.forEach(function(t){t.forEach(p(e.transform))}),e}function s(e){return Math.abs((e[0][0]-e[2][0])*(e[1][1]-e[0][1])-(e[0][0]-e[1][0])*(e[2][1]-e[0][1]))}function f(e,t){return e[1][2]-t[1][2]}function l(e){function t(t){for(var n=a[t];t>0;){var r=(t+1>>1)-1,o=a[r];if(e(n,o)>=0)break;a[o.index=t]=o,a[n.index=t=r]=n}}function n(t){for(var n=a[t];;){var r=t+1<<1,o=r-1,i=t,u=a[i];if(o<a.length&&e(a[o],u)<0&&(u=a[i=o]),r<a.length&&e(a[r],u)<0&&(u=a[i=r]),i===t)break;a[u.index=t]=u,a[n.index=t=i]=n}}var r={},a=[];return r.push=function(){for(var e=0,n=arguments.length;n>e;++e){var r=arguments[e];t(r.index=a.push(r)-1)}return a.length},r.pop=function(){var e=a[0],t=a.pop();return a.length&&(a[t.index=0]=t,n(0)),e},r.remove=function(r){var o=r.index,i=a.pop();return o!==a.length&&(a[i.index=o]=i,(e(i,r)<0?t:n)(o)),o},r}function d(e){var t=0,n=0,r=e.scale[0],a=e.scale[1],o=e.translate[0],i=e.translate[1];return function(e){e[0]=(t+=e[0])*r+o,e[1]=(n+=e[1])*a+i}}function p(e){var t=0,n=0,r=e.scale[0],a=e.scale[1],o=e.translate[0],i=e.translate[1];return function(e){var u=0|(e[0]-o)/r,c=0|(e[1]-i)/a;e[0]=u-t,e[1]=c-n,t=u,n=c}}return{version:"1.3.0pre",mesh:t,feature:n,neighbors:u,presimplify:c}}();