Created
October 22, 2012 05:07
-
-
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, ..
This file contains hidden or 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
| <!-- 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> |
This file contains hidden or 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
| 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 |
This file contains hidden or 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
| @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