Skip to content

Instantly share code, notes, and snippets.

@johan
Created October 22, 2012 05:07
Show Gist options
  • Select an option

  • Save johan/3929758 to your computer and use it in GitHub Desktop.

Select an option

Save johan/3929758 to your computer and use it in GitHub Desktop.
Commute progress tracker prototype - http://codepen.io/johan/pen/fujzI - This is a work in progress. Next few steps:* render time arcs of all discrete transit steps, angle by duration* render space arcs of all discrete transit steps, angle by duration, ..
<!-- codepen doesn't like depending on this(?) -->
<script src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=places,geometry"></script>
<div id="map"></div>
<a id="progress"></a>
<div id="transit-wpr">
<button id="transit">Toggle transit layer</button>
</div>
<div id="panel-wpr">
<div id="info">
<div>
<h2>Transit directions</h2>
</div>
<div>
<label>from:</label>
<input class="input" id="from" value="North Berkeley BART Station, Berkeley, CA, USA">
</div>
<div>
<label>to:</label>
<strong>414 Brannan Street, San Francisco, CA, USA</strong>
</div>
<div>Depart at <select id="depart"></select></div>
<div class="btn">
<button id="go">Get Directions</button>
</div>
</div>
<div id="panel"></div>
</div>
Array::flatten = window.Array::flatten = ->
@reduce (flatten = (a, b) ->
a = [a] unless a instanceof Array
b = b.flatten() if b instanceof Array
a.concat b), []
# d3:
width = 200
height = 200
radius = Math.min(width, height) >> 1
timeArc = d3.svg.arc().outerRadius(radius - 10).innerRadius(radius - 40)
spaceArc = d3.svg.arc().outerRadius(radius - 40).innerRadius(radius - 70)
pie = d3.layout.pie().sort(null).value (d) -> d.duration
drawArcs = ->
svg = d3.select('#progress')
.append('svg').attr('width', width).attr('height', height)
.append('g').attr('transform', "translate(#{width>>1},#{height>>1})")
[{ title: 'time', progress: 0.75, arc: timeArc },
{ title: 'space', progress: 0.80, arc: spaceArc }].forEach (d, i, all) ->
arc = d.arc
g = svg.selectAll(".arc.#{d.title}")
.data(pie([{ duration: d.progress }, { duration: 1-d.progress }]))
.enter()
.append('g').classed("arc #{d.title}", true)
g.append('path').attr('d', arc).style 'fill', (d, i) -> 'transparent' if i
g.append('text').attr('transform', (d) ->
"translate(#{arc.centroid(d)})"
).attr('dy', '.35em').style('text-anchor', 'middle')
.text -> d.title
# google maps:
$ = (id) -> document.getElementById id
GM = window.GM = google.maps
GMEvent = window.GMEvent = GM.event
LatLng = window.LatLng = GM.LatLng
meters = GM.geometry.spherical.computeDistanceBetween
clutter = window.clutter = []
map_div = $('map')
center_at = new LatLng(51.538551, -0.016633)
directions = window.directions = new GM.DirectionsService
renderer = window.renderer = new GM.DirectionsRenderer
hypothenuse = (x, y) -> Math.sqrt x*x + y*y
marker_icon = window.marker_icon = path: GM.SymbolPath.CIRCLE, fillColor: '#000080', scale: 5
transitLayer = map = px_p_m = kdtree = is_here = null
window.LL = LL = (x) -> new LatLng x[0], x[1]
window.ll = ll = (x) -> [x.lat(), x.lng()]
plot = window.plot = (at, icon) ->
at = LL at if at.length
icon = undefined unless typeof icon is 'object'
icon = new GM.Marker map: map, position: at, icon: icon
clutter.push icon
icon
mousemove = (e) ->
window.e = e
at = window.at = e.latLng
window.hovered = hovered = ll at
return unless kdtree
window.nearest = nearest = kdtree.getNearestNeighbour hovered
is_here.setPosition(LL nearest) if is_here
# window.further = further = kdtree.getNearestNeighbours(hovered, 2)[1]
# clutter.push new GM.Polyline map: map, path: [hovered, nearest].map(LL), strokeColor: '#0000ff', strokeOpacity: 0.5
# clutter.push new GM.Polyline map: map, path: [hovered, further].map(LL), strokeColor: '#ff0000', strokeOpacity: 0.5
undefined
GMEvent.addDomListener window, 'load', init = ->
mapOptions = zoom: 14, mapTypeId: GM.MapTypeId.ROADMAP # , center: center_at
map = window.map = new GM.Map $('map'), mapOptions
GMEvent.addListener map, 'zoom_changed', -> GMEvent.addListenerOnce map, 'bounds_changed', updatePath
GMEvent.addDomListener map, 'mousemove', mousemove
GMEvent.addDomListener $('go'), 'click', route
input = $('from')
autocomplete = new GM.places.Autocomplete(input)
autocomplete.bindTo 'bounds', map
transitLayer = new GM.TransitLayer
map.controls[GM.ControlPosition.RIGHT_BOTTOM].push arcs = $('progress')
map.controls[GM.ControlPosition.TOP_RIGHT].push control = $('transit-wpr')
GMEvent.addDomListener control, 'click', ->
transitLayer.setMap (if transitLayer.getMap() then null else map)
addDepartures()
route()
drawArcs()
addDepartures = ->
at = $('depart')
h = 0
while h < 24
m = 0
while m < 60
hh = if h < 10 then "0#{h}" else h
mm = if m < 10 then "0#{m}" else m
at.innerHTML += "<option>#{hh}:#{mm}</option>"
m += 15
h++
route = ->
departure = $('depart').value
now = new Date
tzOffset = (now.getTimezoneOffset() + 60) * 60e3
hhmm = departure.split(':')
time = new Date
time.setHours hhmm[0]
time.setMinutes hhmm[1]
ms = time.getTime() - tzOffset
ms += 24 * 60 * 60e3 if ms < now.getTime()
departureTime = new Date ms
request =
origin: $('from').value
destination: '414 Brannan Street, San Francisco, CA, USA'
travelMode: GM.DirectionsTravelMode.TRANSIT
provideRouteAlternatives: true
transitOptions:
departureTime: departureTime
$('panel').innerHTML = ''
directions.route request, (response, status) ->
window.response = response
window.mystatus = status
updatePath 'new', (if status is GM.DirectionsStatus.OK then response else null)
updatePath = (action, data) ->
console.log 'updatePath'
if action is 'new'
if data is null
renderer.setMap null
renderer.setPanel null
else
renderer.setMap map
renderer.setPanel $('panel')
renderer.setDirections data
leg = window.leg = response.routes[0].legs[0]
is_here = window.is_here = plot leg.start_location, marker_icon
updateInterpolator leg
else
updateInterpolator window.leg = leg if leg
updateInterpolator = () ->
console.log 'updateInterpolator'
bounds = map.getBounds()
return setTimeout updateInterpolator, 100 unless bounds # FIXME?
# how many pixels per meter, for the current map zoom level?
px_p_m = hypothenuse(map_div.offsetWidth, map_div.offsetHeight) /
meters(bounds.getSouthWest(), bounds.getNorthEast())
px = 2
min = px / px_p_m
uniq = {}
orig = [] # ordered uniquified latlng array, as given to us
leg.steps.map((x) -> x.lat_lngs).flatten().forEach (x) ->
if (uniq[x+''] ?= true) ? false
orig.push x
# ditto, but padded out to one for every 5px
path = orig.reduce (a, p2) ->
a = [a] unless a instanceof Array
p1 = a[a.length - 1]
dist = meters p1, p2
if dist <= min
mid = []
else
segments = Math.floor dist / min
dx = (p2.lng() - p1_lng = p1.lng()) / segments
dy = (p2.lat() - p1_lat = p1.lat()) / segments
mid = (new LatLng(p1_lat + dy * n, p1_lng + dx * n) for n in [1..segments])
a.concat mid, p2
# clean up
clutter = window.clutter = clutter.filter (x) ->
if x is is_here
true
else
x.setMap null
window.before = before = orig.map ll
window.coords = coords = path.map ll
window.kdtree = kdtree = new KDTree coords
before.map plot
@import "compass";
#progress {
pointer-events: none;
display: block;
.arc text {
text-shadow: white -1px -1px 0px,
white 1px -1px 0px,
white -1px 1px 0px,
white 1px 1px 0px;
}
.arc.time path {
fill: rgba(200, 0, 0, 0.5);
}
.arc.space path {
fill: rgba(0, 0, 200, 0.5);
}
}
#codepen-footer {
opacity: 0.5;
}
html, body {
font: 10px sans-serif;
height: 100%;
padding: 0;
margin: 0;
color: black;
font-family: arial,sans-serif;
font-size: 13px;
}
#map {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 50%;
}
#panel-wpr {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
right: 0;
overflow: auto;
}
#panel {
font-family: arial;
padding: 0 5px;
}
#info {
padding: 5px;
}
#from {
width: 90%;
font-size: 1.2em;
}
.adp-directions {
width: 100%;
}
.input {
background-color: white;
padding-left: 8px;
border: 1px solid #D9D9D9;
border-top: 1px solid silver;
-webkit-border-radius: 1px;
-moz-border-radius: 1px;
border-radius: 1px;
}
.time {
margin: 0;
height: 17px;
border: 1px solid;
border-top-color: #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #CCC;
padding: 2px 15px 1px 1px;
}
button {
border: 1px solid #3079ED;
color: white;
background-color: #4D90FE;
background-image: -webkit-gradient(linear,left top,left bottom,from(#4D90FE),to(#4787ED));
background-image: -webkit-linear-gradient(top,#4D90FE,#4787ED);
background-image: -moz-linear-gradient(top, #4D90FE,#4787ED);
background-image: -ms-linear-gradient(top,#4D90FE,#4787ED);
background-image: -o-linear-gradient(top,#4D90FE,#4787ED);
background-image: linear-gradient(top,#4D90FE, #4787ED);
filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#4d90fe',EndColorStr='#4787ed');
display: inline-block;
min-width: 54px;
text-align: center;
font-weight: bold;
padding: 0 8px;
line-height: 27px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
-webkit-transition: all 0.218s;
-moz-transition: all 0.218s;
-o-transition: all 0.218s;
transition: all 0.218s;
}
#info div {
line-height: 22px;
font-size: 110%;
}
.btn {
}
#panel-wpr {
border-left: 1px solid #e6e6e6;
}
#info {
border-bottom: 1px solid #E6E6E6;
margin-bottom: 5px;
}
h2 {
margin: 0;
padding: 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment