Skip to content

Instantly share code, notes, and snippets.

@ahwolf
Last active August 29, 2015 13:57
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 ahwolf/9547046 to your computer and use it in GitHub Desktop.
Save ahwolf/9547046 to your computer and use it in GitHub Desktop.
Locating where to have a meetup

This example was created to help me figure out where to hold the location of the Data Science Southeast Michigan meetup.

Please click on Open in new window to view entire visual.

Example is also on github with supporting server side code. link

#map {
height: 667px;
}
svg {
width:700px;
}
.dragging {
fill:rgba(0,255,0,1);
}
#legend_row{
height:200px;
}
#distance{
margin-left: 10px;
}
#distance_traveled{
font-size: 70px;
}
.dragging{
stroke:rgb(96,96,96);
stroke-width: 4px;
}
var data = [{"lat": 42.5868882, "city": "Clinton Township, MI", "lon": -82.9195514, "count": 1}, {"lat": 42.331427, "city": "Detroit, MI", "lon": -83.0457538, "count": 10}, {"lat": 42.5467012, "city": "Birmingham, MI", "lon": -83.21131919999999, "count": 1}, {"lat": 42.5678534, "city": "West Bloomfield, MI", "lon": -83.373339, "count": 1}, {"lat": 42.583645, "city": "Bloomfield Hills, MI", "lon": -83.24548829999999, "count": 1}, {"lat": 42.2411499, "city": "Ypsilanti, MI", "lon": -83.61299389999999, "count": 1}, {"lat": 41.9164343, "city": "Monroe, MI", "lon": -83.3977101, "count": 1}, {"lat": 42.48059, "city": "Novi, MI", "lon": -83.4754913, "count": 1}, {"lat": 42.722257, "city": "Okemos, MI", "lon": -84.4274744, "count": 1}, {"lat": 42.4733688, "city": "Southfield, MI", "lon": -83.2218731, "count": 2}, {"lat": 42.9461395, "city": "Dryden, MI", "lon": -83.12382649999999, "count": 1}, {"lat": 42.49299999999999, "city": "Warren, MI", "lon": -83.02819699999999, "count": 1}, {"lat": 42.6055893, "city": "Troy, MI", "lon": -83.1499304, "count": 1}, {"lat": 42.3086444, "city": "Canton, MI", "lon": -83.48211599999999, "count": 1}, {"lat": 42.36837, "city": "Livonia, MI", "lon": -83.35270969999999, "count": 1}, {"lat": 42.3928151, "city": "Hamtramck, MI", "lon": -83.0496438, "count": 1}, {"lat": 42.0639323, "city": "South Rockwood, MI", "lon": -83.2610406, "count": 1}, {"lat": 42.4644795, "city": "Farmington, MI", "lon": -83.37632180000001, "count": 1}, {"lat": 42.4594803, "city": "Oak Park, MI", "lon": -83.18270509999999, "count": 1}, {"lat": 42.680588, "city": "Rochester, MI", "lon": -83.1338214, "count": 1}, {"lat": 42.5294773, "city": "Brighton, MI", "lon": -83.7802214, "count": 1}, {"lat": 42.3861485, "city": "Grosse Pointe, MI", "lon": -82.9118591, "count": 1}, {"lat": 42.4894801, "city": "Royal Oak, MI", "lon": -83.1446485, "count": 5}, {"lat": 42.2808256, "city": "Ann Arbor, MI", "lon": -83.7430378, "count": 10}, {"lat": 42.2808256, "city": "Sterling Heights, MI", "lon": -83.7430378, "count": 1}, {"lat": 42.5222567, "city": "Franklin, MI", "lon": -83.30604319999999, "count": 1}];
var centroid = [{"lat": 42.31379293333334, "city": "Dual centroid", "lon": -83.65812481111111, "count": ""}, {"lat": 42.52342532941176, "city": "Dual centroid", "lon": -83.16335478235293, "count": ""}];
var single_centroid = [{"lat": 42.442370302714494, "city": "Geomedian approximation", "lon": -83.2267860571865, "count": ""}];
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Meetup locator</title>
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<link rel="stylesheet" href="app.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css">
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.2/leaflet.css">
<link rel="stylesheet" href="http://rawgithub.com/Caged/d3-tip/master/examples/example-styles.css">
</head>
<body>
<div class="row">
<div id="map" class="col-md-6"></div>
<div class="row" id="legend_row">
<div id="legend" class="col-md-6"></div>
</div>
<div class="row">
<div id="distance" class="col-md-3">
Average distance traveled: <p><span id="distance_traveled">17.0</span> miles </p>
<p></p>
<p>Drag the green dot around to view average travel distances for all Meetup data scientists. </p>
</div>
</div>
</div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.2/leaflet.js"></script>
<script type="text/javascript" src="http://maps.stamen.com/js/tile.stamen.js?v1.2.4"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.3.11/d3.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.3/d3-tip.min.js"></script>
<script src="http://d3js.org/colorbrewer.v1.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.2/underscore-min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<!-- // <script src="js/srs_visual.js"></script> -->
<script src="data.js"></script>
<script src="maps.js"></script>
</body>
</html>
var layer = new L.StamenTileLayer("toner");
var lat = 42.36837,
lon = -83.35270969999999; // the coordinates of Livonia, MI
var map = new L.Map("map", {
center: new L.LatLng(lat,lon),
zoom: 10,
minZoom: 10,
maxZoom: 10,
doubleClickZoom: false,
});
map.addLayer(layer);
var svg = d3.select(map.getPanes().overlayPane).append("svg");
var g = svg.append("g").attr("class", "leaflet-zoom-hide");
// get centroid/center of gravity
// var total_count = d3.sum(data, function(d){return d.count})
// var cent_x = d3.sum(data, function(d) {return d2point(d).x * d.count}) / total_count
// var cent_y = d3.sum(data, function(d) {return d2point(d).y * d.count}) / total_count
// var single_centroid = [{x: cent_x,
// y: cent_y,
// city: "Centroid",
// count:""}];
console.log("centroid is: ", single_centroid);
var tip = d3.tip().attr('class', 'd3-tip').html(function(d) { return d.city + " " + d.count; });
svg.call(tip)
var side_length = 60;
var padding = 1;
// These are transformation functions for d3, that allow my lat long
// data to interface with leaflet
function projectPoint(x, y) {
// var point = latLng2point(x,y);
// this.stream.point(point.x, point.y);
this.stream.point(x,y);
}
function latLng2point(x,y){
return map.latLngToLayerPoint(new L.LatLng(y, x));
}
function d2point(d){
return latLng2point(d.lon,d.lat);
}
function point2latLng(x,y){
return map.layerPointToLatLng(L.point(x,y));
}
var transform = d3.geo.transform({point: projectPoint});
var path = d3.geo.path().projection(transform);
var points = _.map(data, function(d){
var xy_point = d2point(d);
return {x: xy_point.x,
y: xy_point.y,
count: d.count}
});
console.log(points);
var drag = d3.behavior.drag()
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
//example
var circle_colors = ["rgba(255,0,0,.8)", "rgba(0,255,0,.5)", "rgba(0,0,255,.5)"];
g.selectAll(".member")
.data(data)
.enter().append("circle")
.attr("class","member")
.attr("cx",function(d){return d2point(d).x; })
.attr("cy",function(d){return d2point(d).y; })
.attr("r", function(d){ return Math.pow(d.count,.5)*5;})
.attr("fill", circle_colors[0])
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
g.selectAll(".centroid")
.data(single_centroid)
.enter().append("circle")
.attr("class","centroid")
.attr("cx",function(d){return d2point(d).x; })
.attr("cy",function(d){return d2point(d).y; })
.attr("r", 25)
.attr("fill", circle_colors[1])
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.call(drag);
g.selectAll(".two_centroid")
.data(centroid)
.enter().append("circle")
.attr("class","two_centroid")
.attr("cx",function(d){return d2point(d).x; })
.attr("cy",function(d){return d2point(d).y; })
.attr("r", 20)
.attr("fill", circle_colors[2])
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
// make the legend for the various circles
var legend_svg = d3.select("#legend").append("svg");
var circle_radius = 20;
var padding = 10;
legend_svg.selectAll("circle")
.data(circle_colors)
.enter().append("circle")
.attr("cx", circle_radius * 1.5)
.attr("cy", function(d, i) { return i*circle_radius*2.2 + circle_radius + padding; })
.attr("r", circle_radius)
.attr("fill", function(d) { return d; });
var text_legend = ["Meetup data scientists", "Optimal single location", "Optimal dual locations"];
legend_svg.selectAll("text")
.data(text_legend)
.enter().append("text")
.text(function(d) { return d; })
.attr("x", circle_radius * 3)
.attr("y", function(d, i) { return i*circle_radius*2.2 + circle_radius + padding + 6; })
.attr("font-family", "Helvetica, Arial, sans-serif")
.attr("font-size", "16px");
// transform the data into lt long
// drag stuff
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
d3.select(this).classed("dragging", true);
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
mean_distance(d);
}
function dragended(d) {
d3.select(this).classed("dragging", false);
}
function mean_distance(d) {
// calculates the mean distance from all other red points, weighted b
// number of data scientists d is an object: d.x = number, d.y =
// number points is a list of objects. each obj has x and
// attribute. multiples, .e.i Ann Arbor, are listed multiple times.
var total_distance = _.reduce(points, function(memo, point){
var distance = point.count * distance_formula(point,d)
return memo + distance; }, 0);
console.log(Math.round(total_distance / 50));
$("#distance_traveled").html((total_distance/50).toFixed(1))
}
function distance_formula(a,b) {
var first = point2latLng(a.x,a.y);
var second = point2latLng(b.x,b.y);
return getDistanceFromLatLonInKm(first.lat, first.lng, second.lat, second.lng);
// return Math.pow( (Math.pow( (b.x - a.x), 2) + Math.pow( (b.y - a.y), 2) ), 0.5)
}
function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
var R = 6371 * 0.621371; // Radius of the earth in miles
var dLat = deg2rad(lat2-lat1); // deg2rad below
var dLon = deg2rad(lon2-lon1);
var a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
Math.sin(dLon/2) * Math.sin(dLon/2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c; // Distance in miles
return d;
}
function deg2rad(deg) {
return deg * (Math.PI/180)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment