Skip to content

Instantly share code, notes, and snippets.

@danwahl
Last active January 31, 2017 06:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save danwahl/45015b04f68d28812094 to your computer and use it in GitHub Desktop.
Save danwahl/45015b04f68d28812094 to your computer and use it in GitHub Desktop.
Divvy Voronoi Diagram
<!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