Created
November 17, 2016 22:58
-
-
Save seb-thomas/d678302063457105edf174acdcccc703 to your computer and use it in GitHub Desktop.
Walking circle for Living Map, uses location API and D3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
L.WalkingCircleLayer = (L.Layer ? L.Layer : L.Class).extend({ | |
initialize: function() { | |
this._prefix = this._prefixMatch(["webkit", "ms", "Moz", "O"]), | |
this._canvasPadding = 4, | |
this._isActive = !1 | |
}, | |
onAdd: function(t) { | |
var a = L.DomUtil.create("div", "leaflet-control-walkingcircle leaflet-bar leaflet-control"); | |
this._link = L.DomUtil.create("a", "leaflet-bar-part leaflet-bar-part-single", a), | |
this._icon = L.DomUtil.create("span", "fa fa-location-arrow", this._link), | |
this._link.href = "#", | |
document.querySelector(".leaflet-right").appendChild(a); | |
var i = this; | |
L.DomEvent.on(this._link, "click", L.DomEvent.stopPropagation).on(this._link, "click", L.DomEvent.preventDefault).on(this._link, "click", function() { | |
this._isActive ? i.onRemove(t) : this._locate() | |
}, this), | |
this._map = t | |
}, | |
onRemove: function(t) { | |
this._isActive = !1, | |
t.getPanes().overlayPane.removeChild(this._canvas), | |
t.stopLocate(), | |
t.off("viewreset moveend resize", this._reset, this), | |
t.off("zoomanim", this._animateZoom, this), | |
t.off("locationfound", this._onLocationFound, this), | |
t.off("locationerror", this._onLocationError, this), | |
L.DomUtil.removeClass(this._link, "active") | |
}, | |
addTo: function(t) { | |
return t.addLayer(this), | |
this | |
}, | |
_initSVG: function() { | |
var t = (this._map.getSize(), | |
d3.select(map.getPanes().overlayPane).append("svg").attr("id", "walking-circle-canvas").attr("class", "svg-zoom-animated")); | |
this.D3_wcContainer = t.append("g").attr("id", "walking-circle-container").attr("visibility", "hidden"), | |
this._canvas = L.DomUtil.get("walking-circle-canvas"), | |
this._drawWalkingCircle(), | |
this._drawPositionCircle(), | |
this._reset(), | |
map.on("viewreset moveend resize", this._reset, this) | |
}, | |
_reset: function(t) { | |
var a = this._map.getSize() | |
, i = a.x * this._canvasPadding | |
, e = a.y * this._canvasPadding | |
, r = -(i - a.x) / 2 | |
, n = -(e - a.y) / 2 | |
, o = this._map.containerPointToLayerPoint([r, n]).round(); | |
this._canvas.setAttribute("width", i), | |
this._canvas.setAttribute("height", e), | |
this._canvas.setAttribute("viewBox", [o.x, o.y, i, e].join(" ")), | |
L.DomUtil.setPosition(this._canvas, o); | |
var s = this; | |
void 0 !== t && "viewreset" == t.type && t.hard && this.D3_wcContainer.attr({ | |
style: function() { | |
var t = map.latLngToLayerPoint(s._latlng); | |
return s._prefix + "transform: translate(" + t.x + "px," + t.y + "px)" | |
} | |
}) | |
}, | |
_drawWalkingCircle: function() { | |
this._count = 1; | |
this.D3_wcContainer.append("g").attr("id", "walking-circle").attr("transform", "rotate(0)"); | |
this.D3_wc = d3.select("#walking-circle"), | |
this._minsPerZoom = { | |
15: [1, 2, 5], | |
14: [2, 5, 10], | |
13: [3, 8, 15], | |
12: [5, 12, 30], | |
11: [10, 25, 45], | |
10: [15, 30, 60], | |
9: [25, 40, 60], | |
8: [25, 50, 75], | |
7: [30, 75, 120] | |
}, | |
this._zoomToRadius = {}; | |
for (var t = 15, a = 7, i = 1, e = t; e >= a; e--) | |
this._zoomToRadius[e] = 672 / i, | |
i += i; | |
this._arc = d3.svg.arc().startAngle(0); | |
var r = this; | |
this._minsPerZoom[this._map.getZoom()].forEach(function(t) { | |
r._makeCircle(t) | |
}) | |
}, | |
_makeCircle: function(t) { | |
var a = this.D3_wc | |
, i = 2 * Math.PI | |
, e = this._map.getZoom() | |
, r = this._zoomToRadius[e] | |
, n = this._arc | |
, o = this._calcArc(r * t, 3) | |
, s = a.append("g").attr("class", "circle-container") | |
, c = (s.append("path").datum({ | |
endAngle: i, | |
innerRadius: o.innerRadius, | |
outerRadius: o.outerRadius | |
}).style("fill", "white").attr("class", "circle circle" + this._count).attr("d", n), | |
s.append("path").datum({ | |
endAngle: i / 16, | |
innerRadius: o.tagInnerRadius, | |
outerRadius: o.tagOuterRadius | |
}).style("fill", "white").attr("d", n).attr("class", "tag").attr("id", "tag" + this._count), | |
s.append("text").attr("x", 6).attr("dy", 12)); | |
c.append("textPath").attr("class", "walking-circle-text").attr("xlink:href", "#tag" + this._count).text(t + " mins walk"), | |
this._count += 1 | |
}, | |
_drawPositionCircle: function() { | |
{ | |
var t = this.D3_wcContainer.append("g").attr("id", "position-circle").attr("transform", "rotate(0)") | |
, a = d3.svg.line().x(function(t) { | |
return t.x | |
}).y(function(t) { | |
return t.y | |
}).interpolate("linear") | |
, i = { | |
arrow: { | |
width: 14, | |
height: 26, | |
tip: 14 | |
}, | |
circle: { | |
radius: 40 | |
} | |
} | |
, e = [{ | |
x: i.arrow.width, | |
y: i.arrow.height | |
}, { | |
x: i.arrow.width / 2, | |
y: 0 | |
}, { | |
x: 0, | |
y: i.arrow.height | |
}, { | |
x: i.arrow.width / 2, | |
y: i.arrow.height - 5 | |
}]; | |
t.append("circle").attr("id", "pc-circle").attr("r", i.circle.radius).attr("fill", "none").attr("stroke", "white").attr("stroke-width", 4).attr("stroke-linecap", "round").attr("stroke-dasharray", "0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 7, 0.5, 33"), | |
t.append("path").attr("id", "pc-letter-n").attr("d", "M3.8,5.4L1.3,2.2v3.3H0V0h1.2l2.5,3.3V0H5v5.4H3.8z").attr("fill", "white").attr("transform", "translate(1, -60) scale(0)"), | |
t.append("path").attr("id", "pc-arrow").attr("d", a(e) + "Z").attr("stroke", "white").attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("fill", "white").style("fill-opacity", 0).attr("transform", "translate(-6, " + -(i.circle.radius + i.arrow.tip) + ") scale(1)"), | |
t.append("path").attr("id", "pc-bars").attr("d", "M40,0 L31,0 M0,40 L0,31 M-40,0 L-31,0").attr("stroke", "white").attr("stroke-width", 4).attr("stroke-linecap", "round") | |
} | |
}, | |
_locate: function() { | |
L.DomUtil.addClass(this._link, "finding"), | |
map.on("locationfound", this._onLocationFound, this), | |
map.once("locationfound", function(t) { | |
map.panTo(params.demo ? [CONFIG.startCenter.lat, CONFIG.startCenter.lng] : t.latlng) | |
}, this), | |
map.on("locationerror", this._onLocationError, this), | |
this._isActive = !0, | |
this._initSVG(), | |
map.locate({ | |
watch: !0 | |
}), | |
map.on("zoomanim", this._animateZoom, this), | |
window.setTimeout(function() { | |
var t = 0 | |
, a = 0; | |
if ("ondeviceorientation"in window) { | |
window.addEventListener("deviceorientation", function(i) { | |
var e, r, n; | |
for ("undefined" != typeof i.webkitCompassHeading ? (e = i.webkitCompassHeading, | |
"undefined" != typeof window.orientation && (e += window.orientation)) : e = 360 - i.alpha, | |
r = Math.round(e) - a, | |
a = Math.round(e), | |
-180 > r && (r += 360), | |
r > 180 && (r -= 360), | |
t += r, | |
n = e; n >= 360; ) | |
n -= 360; | |
for (; 0 > n; ) | |
n += 360; | |
n = Math.round(n) | |
}); | |
var i, e = d3.select("#position-circle"), r = d3.select("#pc-arrow"), n = d3.select("#pc-letter-n"), o = r.attr("transform"), s = n.attr("transform"); | |
window.setInterval(function() { | |
var a = -t | |
, c = Math.abs(a - i); | |
c >= 1 && (a > -8 && 8 > a ? (e.transition().duration(300).ease("bounce").attr("transform", "rotate(0)"), | |
r.transition().duration(200).ease("elastic").attr("transform", "translate(-8, -58) scale(1.3)").style("fill-opacity", 1).transition().duration(200).ease("out").attr("transform", o), | |
n.transition().duration(200).ease("elastic").attr("transform", "translate(-5, -78) scale(2.5)").transition().duration(200).ease("out").attr("transform", s)) : (e.transition().duration(500).attr("transform", "rotate(" + a + ")"), | |
r.transition().attr("transform", o).style("fill-opacity", 0))), | |
i = a | |
}, 200) | |
} | |
}, 400) | |
}, | |
_onLocationFound: function(t) { | |
this._latlng = params.demo ? [CONFIG.startCenter.lat, CONFIG.startCenter.lng] : [t.latitude, t.longitude]; | |
var a = this; | |
L.DomUtil.addClass(this._link, "active"), | |
L.DomUtil.removeClass(this._link, "finding"), | |
this.D3_wcContainer.attr("visibility", "visible").attr({ | |
style: function() { | |
var t = map.latLngToLayerPoint(a._latlng); | |
return a._prefix + "transform: translate(" + t.x + "px," + t.y + "px)" | |
} | |
}) | |
}, | |
_onLocationError: function(t) { | |
alert(t.message) | |
}, | |
_animateZoom: function(t) { | |
var a = t.zoom | |
, i = this; | |
this.D3_wcContainer.attr({ | |
style: function() { | |
var e = map._latLngToNewLayerPoint(i._latlng, a, t.center); | |
return i._prefix + "transform: translate(" + e.x + "px," + e.y + "px)" | |
} | |
}); | |
var e = this._minsPerZoom[a] | |
, r = this._zoomToRadius[a]; | |
d3.selectAll(".circle-container").each(function(t, a) { | |
var n = e[a] | |
, o = i._calcArc(r * n, 3) | |
, s = n + (1 == n ? " min walk" : " mins walk"); | |
d3.select(this).select(".circle").transition().duration(750).ease("elastic").call(i._arcTween, i._arc, o.innerRadius, o.outerRadius), | |
d3.select(this).select(".tag").transition().duration(750).ease("elastic").call(i._arcTween, i._arc, o.tagInnerRadius, o.tagOuterRadius), | |
d3.select(this).select(".walking-circle-text").text(s) | |
}) | |
}, | |
_arcTween: function(t, a, i, e) { | |
t.attrTween("d", function(t) { | |
var r = d3.interpolate(t.innerRadius, i) | |
, n = d3.interpolate(t.outerRadius, e); | |
return function(i) { | |
return t.outerRadius = n(i), | |
t.innerRadius = r(i), | |
a(t) | |
} | |
}) | |
}, | |
_calcArc: function(t, a) { | |
var i = { | |
innerRadius: t, | |
outerRadius: t + a, | |
tagInnerRadius: t - a - 12, | |
tagOuterRadius: t + 1 | |
}; | |
return i | |
}, | |
_prefixMatch: function(t) { | |
for (var a = -1, i = t.length, e = document.body.style; ++a < i; ) | |
if (t[a] + "Transform"in e) | |
return "-" + t[a].toLowerCase() + "-"; | |
return "" | |
} | |
}), | |
L.walkingCircle = function() { | |
return new L.WalkingCircleLayer | |
} | |
; | |
var wc = L.walkingCircle().addTo(map); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment