Last active
January 31, 2017 06:47
-
-
Save danwahl/45015b04f68d28812094 to your computer and use it in GitHub Desktop.
Divvy Voronoi Diagram
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
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"> | |
<html> | |
<head> | |
<meta name="generator" content= | |
"HTML Tidy for Windows (vers 14 February 2006), see www.w3.org"> | |
<meta name="viewport" content="initial-scale=1.0, user-scalable=no"> | |
<style type="text/css"> | |
html { height: 100% } | |
body { height: 100%; margin: 0; padding: 0 } | |
#map-canvas { height: 100% } | |
#bikesUI, #docksUI { | |
background-color: #fff; | |
border: 1px solid #ddd; | |
cursor: pointer; | |
float: left; | |
text-align: center; | |
} | |
#bikesText, #docksText { | |
color: rgb(25,25,25); | |
font-family: Roboto,Arial,sans-serif; | |
font-size: 16px; | |
line-height: 25px; | |
padding: 10px; | |
} | |
.control { | |
} | |
.control.Selected { | |
font-weight: bold; | |
} | |
</style> | |
<script src="https://d3js.org/d3.v3.min.js" type="text/javascript"></script> | |
<script type="text/javascript" src= | |
"https://maps.googleapis.com/maps/api/js?key=AIzaSyAbM8EJ5BxK80eOtDUTBtBflW77ISJrfic"></script> | |
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> | |
<script type="text/javascript"> | |
// borrowed some of this from https://www.divvybikes.com/assets/js/chicago/stations.js | |
var map; | |
var infowindow; | |
var bounds; | |
var stationList; | |
var fillOpacity = 0.3; | |
var iconScale = 0.5; | |
var divvyColor = '#38b5e6'; | |
var im = 'https://www.robotwoods.com/dev/misc/bluecircle.png'; | |
var userPos; | |
function stationControl(controlDiv, map, bikes) { | |
// We set up a variable for this since we're adding event listeners | |
// later. | |
var control = this; | |
// Set the center property upon construction | |
controlDiv.style.clear = 'both'; | |
// Set CSS for the control border | |
var bikesUI = document.createElement('div'); | |
bikesUI.id = 'bikesUI'; | |
bikesUI.title = 'Click for available Bikes'; | |
bikesUI.className = 'control'; | |
if(bikes) bikesUI.className += ' Selected'; | |
controlDiv.appendChild(bikesUI); | |
// Set CSS for the control interior | |
var bikesText = document.createElement('div'); | |
bikesText.id = 'bikesText'; | |
bikesText.innerHTML = 'Bikes'; | |
bikesUI.appendChild(bikesText); | |
// Set CSS for the setCenter control border | |
var docksUI = document.createElement('div'); | |
docksUI.id = 'docksUI'; | |
docksUI.title = 'Click for available Docks'; | |
docksUI.className = 'control'; | |
if(!bikes) docksUI.className += ' Selected'; | |
controlDiv.appendChild(docksUI); | |
// Set CSS for the control interior | |
var docksText = document.createElement('div'); | |
docksText.id = 'docksText'; | |
docksText.innerHTML = 'Docks'; | |
docksUI.appendChild(docksText); | |
// Set up the click event listener for 'Center Map': Set the center of | |
// the map | |
// to the current center of the control. | |
bikesUI.addEventListener('click', function() { | |
bikesUI.className = 'control Selected'; | |
docksUI.className = 'control'; | |
clearMap(); | |
drawMap(true); | |
}); | |
// Set up the click event listener for 'Set Center': Set the center of | |
// the control to the current center of the map. | |
docksUI.addEventListener('click', function() { | |
docksUI.className = 'control Selected'; | |
bikesUI.className = 'control'; | |
clearMap(); | |
drawMap(false); | |
}); | |
} | |
// http://code.tutsplus.com/tutorials/quick-tip-cross-domain-ajax-request-with-yql-and-jquery--net-10225 | |
// Accepts a url and a callback function to run. | |
function requestCrossDomain(site, callback) { | |
// If no url was passed, exit. | |
if (!site) { | |
alert('No site was passed.'); | |
return false; | |
} | |
// Take the provided url, and add it to a YQL query. Make sure you encode it! | |
var yql = 'https://query.yahooapis.com/v1/public/yql?q=' + encodeURIComponent('select * from html where url="' + site + '"') + '&format=xml'; | |
// Request that YSQL string, and run a callback function. | |
// Pass a defined function to prevent cache-busting. | |
$.get(yql, cbFunc); | |
function cbFunc(data) { | |
// parse xml response | |
var xml = data.documentElement.outerHTML, | |
xmlDoc = $.parseXML(xml), | |
$xml = $(xmlDoc), | |
$body = $xml.find('body'); | |
// If we have something to work with... | |
if($body.text()) { | |
// If the user passed a callback, and it | |
// is a function, call it, and send through the data var. | |
if(typeof callback === 'function') { | |
//callback($body.text()); | |
var json = $.parseJSON($body.text()); | |
callback(json); | |
} | |
} | |
// Else, Maybe we requested a site that doesn't exist, and nothing returned. | |
else throw new Error('Nothing returned from getJSON.'); | |
} | |
} | |
// clear markers and polygons | |
function clearMap() { | |
for(var j = 0; j < stationList.active.length; j++) { | |
if(stationList.active[j].marker) stationList.active[j].marker.setMap(null); | |
if(stationList.active[j].polygon) stationList.active[j].polygon.setMap(null); | |
} | |
for(var j = 0; j < stationList.inactive.length; j++) { | |
if(stationList.inactive[j].marker) stationList.inactive[j].marker.setMap(null); | |
} | |
} | |
// draw map | |
function drawMap(bikes) { | |
// initialize station list | |
stationList = {active: [], inactive: []}; | |
//$.getJSON("stations.json", function(stations) { | |
requestCrossDomain('feeds.divvybikes.com/stations/stations.json', function(stations) { | |
// iterate through each station | |
$.each(stations.stationBeanList, function(i, station) { | |
// get marker location | |
var point = new google.maps.LatLng(station.latitude, station.longitude); | |
bounds.extend(point); | |
var image = 'https://member.divvybikes.com/assets/images/chicago/icons/stations/map-icon-inservice-legend.png' | |
var active = true; | |
if((!bikes && (station.availableDocks == 0)) || (bikes && (station.availableBikes == 0))) { | |
image = 'https://member.divvybikes.com/assets/images/chicago/icons/stations/map-icon-inservice-legend.png'; | |
active = false; | |
} | |
// create marker | |
var marker = new google.maps.Marker({ | |
icon: { | |
url: image, | |
scaledSize: new google.maps.Size(41*iconScale, 53*iconScale) | |
}, | |
position : point, | |
map : map, | |
title : station.stationName | |
}); | |
var contentString = station.stationName + '<br> ' + 'Available Bikes: ' + station.availableBikes + '<br> ' + 'Available Docks: ' + station.availableDocks; | |
// add listener to marker click | |
google.maps.event.addListener(marker, 'click', function() { | |
infowindow.setContent(contentString); | |
infowindow.open(map, this); | |
// iterate through station list | |
for(var j = 0; j < stationList.length; j++) { | |
var opacity = 0.0; | |
// check if this marker was clicked | |
if(this == stationList[j].marker) { | |
opacity = fillOpacity; | |
} | |
// set opacity | |
stationList[j].polygon.setOptions({ | |
fillOpacity: opacity | |
}); | |
} | |
}); | |
// add marker to station list | |
s = {marker: marker, name: station.stationName, position: [station.latitude, station.longitude], content: contentString} | |
if((!bikes && (station.availableDocks == 0)) || (bikes && (station.availableBikes == 0))) stationList.inactive.push(s); | |
else stationList.active.push(s); | |
}) | |
// generate voronoi polygons | |
voronoiDiagram(userPos); | |
}); | |
} | |
// initialize call for window load | |
function initialize() { | |
// specify location | |
var chicago = new google.maps.LatLng(41.898188, -87.631073); | |
userPos = chicago; | |
// show bikes first by default | |
var type = 'bikes'; | |
// create map and info window | |
var mapOptions = { | |
center: chicago, | |
zoom: 14 | |
}; | |
map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions); | |
infowindow = new google.maps.InfoWindow(); | |
// keep track of bounds for recentering the map | |
bounds = new google.maps.LatLngBounds(); | |
// create controls | |
var stationControlDiv = document.createElement('div'); | |
var control = new stationControl(stationControlDiv, map, type); | |
// show controls | |
stationControlDiv.index = 1; | |
//stationControlDiv.style['padding-top'] = '10px'; | |
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(stationControlDiv); | |
// get location | |
// Try HTML5 geolocation. | |
if (navigator.geolocation) { | |
navigator.geolocation.getCurrentPosition(function(position) { | |
var pos = { | |
lat: position.coords.latitude, | |
lng: position.coords.longitude | |
}; | |
map.setCenter(pos); | |
var userMarker = new google.maps.Marker({ | |
position: pos, | |
map: map, | |
icon: im | |
}); | |
// store user position | |
userPos = pos; | |
// draw map | |
drawMap(type); | |
}, function() { | |
// couldn't get position, draw map anyway | |
drawMap(type); | |
}); | |
} else { | |
// Browser doesn't support Geolocation draw map anyway | |
drawMap(type); | |
} | |
} | |
// from https://github.com/substack/point-in-polygon | |
function pointInPolygon(point, vs) { | |
// ray-casting algorithm based on | |
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html | |
var xi, xj, i, intersect, | |
x = point[0], | |
y = point[1], | |
inside = false; | |
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { | |
xi = vs[i][0], | |
yi = vs[i][1], | |
xj = vs[j][0], | |
yj = vs[j][1], | |
intersect = ((yi > y) != (yj > y)) | |
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi); | |
if (intersect) inside = !inside; | |
} | |
return inside; | |
} | |
function voronoiDiagram(pos) { | |
// generate voronio polygons | |
var widthAdj = (bounds.getNorthEast().lng() - bounds.getSouthWest().lng())/2.0; | |
var heightAdj = (bounds.getNorthEast().lat() - bounds.getSouthWest().lat())/2.0; | |
var voronoi = d3.geom.voronoi().clipExtent([[bounds.getSouthWest().lat() - heightAdj, bounds.getSouthWest().lng() - widthAdj], [bounds.getNorthEast().lat() + heightAdj, bounds.getNorthEast().lng() + widthAdj]]); | |
var positions = []; | |
for(i = 0; i < stationList.active.length; i++) { | |
positions.push(stationList.active[i].position); | |
} | |
var polygons = voronoi(positions); | |
// iterate through polygons, draw on map | |
if(!polygons) return false; | |
for(var i = 0; i < polygons.length; i++) { | |
var voronoiCoords = []; | |
// store coordinates for each polygon | |
for(var j = 0; j < polygons[i].length; j++) { | |
voronoiCoords.push(new google.maps.LatLng(polygons[i][j][0], polygons[i][j][1])); | |
} | |
// complete polygon | |
voronoiCoords.push(new google.maps.LatLng(polygons[i][0][0], polygons[i][0][1])); | |
// create google maps polygon and draw | |
var voronoiPolygon = new google.maps.Polygon({ | |
paths: voronoiCoords, | |
strokeColor: divvyColor, | |
strokeOpacity: fillOpacity, | |
strokeWeight: 3, | |
fillColor: divvyColor, | |
fillOpacity: 0.0 | |
}); | |
voronoiPolygon.setMap(map); | |
// add listener to polygon click | |
google.maps.event.addListener(voronoiPolygon, 'click', function(event) { | |
// iterate through station list | |
for(var j = 0; j < stationList.active.length; j++) { | |
var opacity = 0.0; | |
// check if this marker was clicked | |
if(this == stationList.active[j].polygon) { | |
opacity = fillOpacity; | |
// open marker's info window | |
infowindow.setContent(stationList.active[j].content); | |
infowindow.open(map, stationList.active[j].marker); | |
} | |
// set opacity | |
stationList.active[j].polygon.setOptions({ | |
fillOpacity: opacity | |
}); | |
} | |
}); | |
stationList.active[i].polygon = voronoiPolygon; | |
// check for closest station to geolocation | |
if(pos) { | |
if(pointInPolygon([pos.lat, pos.lng], polygons[i])) { | |
infowindow.setContent(stationList.active[i].content); | |
infowindow.open(map, stationList.active[i].marker); | |
stationList.active[i].polygon.setOptions({ | |
fillOpacity: fillOpacity | |
}); | |
} | |
} | |
} | |
} | |
// call initialize on window load | |
google.maps.event.addDomListener(window, 'load', initialize); | |
</script> | |
<title></title> | |
</head> | |
<body> | |
<div id="map-canvas"> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment