Here we compare the geospatial distribution between two terms in OpenStreetMap points of interest. Click "Next" to load a new example comparison. Or put any words you want in the input boxes to see where they occur in OSM.
Last active
August 29, 2015 14:23
-
-
Save mapsense-examples/27e9f86aaf66519ec128 to your computer and use it in GitHub Desktop.
query OSM points 1.1
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
html, body, #myMap { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
overflow: hidden; | |
font: 11px 'Droid Sans', sans-serif; | |
color: #666; | |
} | |
* { box-sizing: border-box } | |
.ms { | |
stroke-width: 1; | |
stroke: #ccc; | |
stroke-opacity: 0.8; | |
fill-opacity: 0.5; | |
mix-blend-mode: multiply; | |
/* | |
*/ | |
} | |
circle { | |
vector-effect: non-scaling-stroke; | |
} | |
.A { | |
stroke: cyan; | |
fill: cyan; | |
/* | |
stroke-width: 0; | |
*/ | |
} | |
.B { | |
stroke: magenta; | |
fill: magenta; | |
/* | |
stroke-width: 0; | |
*/ | |
} | |
a.button { | |
display: inline-block; | |
padding: 5px 10px; | |
border: 2px solid #777; | |
font: 16px bold 'Droid Sans', sans-serif; | |
line-height: 37px; | |
color: #777; | |
height: 50px; | |
min-width: 50px; | |
vertical-align: top; | |
margin-left: 4px; | |
text-align: center; | |
cursor: pointer; | |
} | |
a.button:hover { | |
border: 2px solid #000; | |
color: #000; | |
} | |
input { | |
border: 0s; | |
font: 16px 'Droid Sans', sans-serif; | |
} | |
input:focus { | |
outline-offset: 0px; | |
} | |
#refresh { margin-top: 5px;} | |
#selector_a { | |
outline: 13px solid rgba(0,255,255,0.6); | |
mix-blend-mode: multiply; | |
margin: 13px; | |
} | |
#selector_b { | |
outline: 13px solid rgba(255,0,255,0.6); | |
mix-blend-mode: multiply; | |
margin: 13px; | |
margin-left: 0px; | |
} | |
#ui { | |
position: absolute; | |
z-index: 99; | |
margin: 0; | |
top: 0; | |
left: 0; | |
clear: both; | |
} | |
#selector { | |
font: 20px 'Droid Sans', sans-serif; | |
} | |
#legend { | |
background: rgba(255,255,255,0.8); | |
overflow: auto; | |
display: inline-block; | |
} | |
#color-chips, #color-values { | |
float: left; | |
margin-left: 5px; | |
} | |
#color-values{ | |
white-space: pre; | |
padding-right: 5px; | |
} | |
#color-values textarea { | |
overflow:hidden; | |
margin: 0; | |
border: none; | |
resize: none; | |
height: 100%; | |
} | |
.mouseinfo { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
pointer-events: none; | |
max-width: 300px; | |
/*font: 20px 'Droid Sans', sans-serif;*/ | |
} | |
table { | |
border-collapse: collapse; | |
} | |
table, th, td { | |
border: 1px solid #ddd; | |
padding: 0 2px; | |
} | |
.detailKey { | |
background: #eee; | |
opacity: .8; | |
text-transform: uppercase; | |
font-weight: 600; | |
} | |
.detailVal { | |
background: rgba(255,255,255,0.8); | |
} |
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> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script src="http://d3js.org/topojson.v1.min.js" charset="utf-8"></script> | |
<script src="https://developer.mapsense.co/mapsense.js" charset="utf-8"></script> | |
<link type="text/css" href="https://developer.mapsense.co/mapsense.css" rel="stylesheet"/> | |
<link type="text/css" href="index.css" rel="stylesheet"/> | |
<style> | |
circle { | |
vector-effect: non-scaling-stroke; | |
/*We leave them unpainted until they match a regex later*/ | |
stroke: none; | |
fill: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="myMap"></div> | |
<div id="ui"> | |
<div id="control"></div> | |
<div id="legend"> | |
</div> | |
</div> | |
<script> | |
// TODO: read inputs from hash | |
mapsense.hash = function() { // overriding the hash function to include input variables | |
var hash = {}, | |
s0, // cached location.hash | |
lat = 90 - 1e-8, // allowable latitude range | |
map; | |
var parser = function(map, s) { | |
var args = s.split("/").map(Number); | |
if (args.length < 3 || args.some(isNaN)) return true; // replace bogus hash | |
else { | |
var size = map.size(); | |
map.zoomBy(args[0] - map.zoom(), | |
{x: size.x / 2, y: size.y / 2}, | |
{lat: Math.min(lat, Math.max(-lat, args[1])), lon: args[2]}); | |
} | |
}; | |
var formatter = function(map) { | |
var center = map.center(), | |
zoom = map.zoom(), | |
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); | |
return "#" + zoom.toFixed(2) + | |
"/" + center.lat.toFixed(precision) + | |
"/" + center.lon.toFixed(precision) + | |
"/" + d3.select('#selector_a').node().value + | |
"/" + d3.select('#selector_b').node().value | |
}; | |
function move() { | |
var s1 = formatter(map); | |
if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! | |
} | |
function hashchange() { | |
//if (location.hash === s0) return; // ignore spurious hashchange events | |
if (parser(map, (s0 = location.hash).substring(1))) | |
move(); // replace bogus hash | |
} | |
hash.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
map.off("move", move); | |
window.removeEventListener("hashchange", hashchange, false); | |
} | |
if (map = x) { | |
map.on("move", move); | |
window.addEventListener("hashchange", hashchange, false); | |
d3.selectAll('.selector').on('change.hash',function() { | |
hashchange(); | |
}); | |
if (location.hash) | |
hashchange(); | |
else | |
move(); | |
} | |
return hash; | |
}; | |
hash.parser = function(x) { | |
if (!arguments.length) return parser; | |
parser = x; | |
return hash; | |
}; | |
hash.formatter = function(x) { | |
if (!arguments.length) return formatter; | |
formatter = x; | |
return hash; | |
}; | |
return hash; | |
}; | |
var G = {}; // A global object to store variables | |
G.key = 'key-2d5eacd8b924489c8ed5e8418bd883bc'; | |
G.simplify = '&ringSpan=10&lineSpan=10&s=10'; | |
G.home = [ // we'll set the map extent to these bounds | |
{lon: -125, lat: -60}, | |
{lon: 160, lat: 75} | |
]; | |
G.basemap = 'sketch'; | |
G.input_A = 'kitten'; | |
G.input_B = 'puppy'; | |
G.samples = [ | |
['kitten','puppy'], | |
['bar','pub'], | |
['soda','pop'], | |
['cafe','caffe'], | |
['church','mosque'], | |
['ice cream','gelato'], | |
['Martin Luther King','Jefferson Davis'], | |
['bicycle','motorcycle'], | |
['east','west'] | |
]; | |
G.samples_i = 0; | |
G.layers = { | |
'base': { 'url': '', 'default': false}, | |
'overlay_a': { | |
'url': 'https://{S}-api.mapsense.co/universes/mapsense.planet_osm_points/{Z}/{X}/{Y}.topojson?api-key=' + G.key, | |
'where': "&where=name=='"+G.input_A+"' OR name=='"+G.input_B+"'", | |
'params': '&density=200', | |
'class': '', | |
'default': true | |
}, | |
'labels': { 'url': '', 'default': false} | |
}; | |
G.selector_options = ['place_of_worship','school','bench','restaurant','fast_food','cafe','bicycle_parking','pub','bar','swimming_pool','university','college']; | |
G.selector_options.sort(); | |
initSelect(); // initialize the selector UI | |
initMap(); // initialize the map | |
// Add a div to display info mouseover info | |
var mouseinfo = d3.select('body') | |
.append("div") | |
.attr("class","mouseinfo"); | |
function initMap() { | |
var param_selection_function = (function(show_list) { | |
return function(s) { | |
s.attr("r", 3); | |
s.attr("class", function(f){ | |
var classes = ['ms']; | |
classes.push(G.layers['overlay_a']['class']); | |
//var show_list = ['amenity','tourism','name']; | |
var show_list = ['name']; | |
for (var i = 0; i < show_list.length; i++) { | |
if (f.properties && f.properties[show_list[i]]) { | |
classes.push(f.properties[show_list[i]]); | |
/*if ( f.properties[show_list[i]] == d3.select('#selector_a').node().value ) { | |
classes.push('A'); | |
} else if ( f.properties[show_list[i]] == d3.select('#selector_b').node().value ) { | |
classes.push('B'); | |
}*/ | |
var feature_value = f.properties[show_list[i]]; | |
var A_value = d3.select('#selector_a').node().value; | |
var B_value = d3.select('#selector_b').node().value; | |
var A_regex = new RegExp(escapeRegExp(A_value), "gi"); | |
var B_regex = new RegExp(escapeRegExp(B_value), "gi"); | |
// at this point, the line above is the same as: var regex = /#abc#/g; | |
if ( A_regex.test(feature_value) ) { | |
classes.push('A'); | |
} else if ( B_regex.test(feature_value) ) { | |
classes.push('B'); | |
} | |
} | |
}; | |
return classes.join(' '); | |
}) | |
s.on("mouseover", function(d) { // Bind a function to mouseover | |
// This will build a table to display the field names and values | |
var text = ""; | |
var value; | |
var no_list = ['DND_UUID']; | |
text += '<div class="detailCard"><table><tbody>'; | |
if (show_list.length > 0) { // if there's a show_list, only show those fields | |
for (var i = 0; i < show_list.length; i++) { | |
key = show_list[i]; | |
if (d.properties && d.properties[key]) { | |
value = d.properties[key]; | |
value = formatValue(value); | |
text += '<tr><td class="detailKey">' + key + '</td><td class="detailVal">' + value + '</td></tr>'; | |
} | |
} | |
} else { // if no show_list, show all fields | |
for (var key in d.properties) { | |
// except if it's in the no_list | |
if ( no_list.indexOf(key.toUpperCase()) == -1 ) { | |
text += '<tr><td class="detailKey">' + key + '</td><td class="detailVal">' + d.properties[key] + '</td></tr>'; | |
} | |
} | |
} | |
mouseinfo.html(text); // Update the mouseinfo div with the dynamic info | |
G.layers['overlay_a'].ms_layer.selection(param_selection_function([])); | |
}); | |
}; | |
}); | |
map = mapsense.map("#myMap"); // init the map | |
map.extent(G.home); | |
if (G.basemap) { | |
map.add(mapsense.basemap().apiKey(G.key).style(G.basemap)); | |
} | |
/* | |
We want to use one overlay layer for both the queries | |
so we'll see their relative proportions. | |
We need to class them either A or B. | |
*/ | |
G.layers['overlay_a'].ms_layer = mapsense.topoJson() | |
.url(mapsense.url( | |
G.layers['overlay_a'].url + G.layers['overlay_a'].where + G.layers['overlay_a'].params | |
) | |
.hosts(['a', 'b', 'c', 'd'])) | |
.clip(false) | |
.scale('fixed') | |
.selection( | |
param_selection_function([]) | |
); | |
map.add(G.layers['overlay_a'].ms_layer); | |
// change map interaction so users can see the map update when they scroll through the selector fields | |
map.interact(false); | |
map.add(mapsense.drag()); | |
map.add(mapsense.wheel()); | |
map.add(mapsense.dblclick()); | |
map.add(mapsense.touch()); | |
map.add(mapsense.hash()); | |
mapsense.compass().map(map); //enable shift zoom | |
d3.select('.compass').attr('style','display: none;') // but hide the compass graphic | |
//$("#selector").val(G.FIELD).change(); // trigger the selector | |
} | |
function updateQuery() { | |
// update the query | |
//var new_where = "&where=amenity=='" + d3.select('#selector_a').node().value + "' OR amenity=='" + d3.select('#selector_b').node().value + "'"; | |
var valu_A = d3.select('#selector_a').node().value; | |
var valu_B = d3.select('#selector_b').node().value; | |
var new_where = "&where=name=='" + valu_A + "' OR name=='" + valu_B + "'"; | |
var new_url = G.layers['overlay_a'].url + new_where + G.layers['overlay_a'].params; | |
// update the layer | |
G.layers['overlay_a'].ms_layer.url( | |
mapsense.url(new_url) | |
.hosts(['a','b','c','d']) | |
); | |
} | |
function cycleQuery() { | |
// update the query | |
//var new_where = "&where=amenity=='" + d3.select('#selector_a').node().value + "' OR amenity=='" + d3.select('#selector_b').node().value + "'"; | |
if (G.samples_i < G.samples.length-1) { | |
G.samples_i += 1; | |
} else { | |
G.samples_i = 0; | |
} | |
var valu_A = G.samples[G.samples_i][0]; | |
var valu_B = G.samples[G.samples_i][1]; | |
d3.select('#selector_a').node().value = valu_A; | |
d3.select('#selector_b').node().value = valu_B; | |
d3.select('#enter').on('click')(); | |
/* var new_where = "&where=name=='" + valu_A + "' OR name=='" + valu_B + "'"; | |
var new_url = G.layers['overlay_a'].url + new_where + G.layers['overlay_a'].params; | |
// update the layer | |
G.layers['overlay_a'].ms_layer.url( | |
mapsense.url(new_url) | |
.hosts(['a','b','c','d']) | |
);*/ | |
} | |
function initSelect() { | |
// add a select element to the page | |
d3.select('#control').append('input') | |
.attr('id','selector_a') | |
.attr('class','selector A') | |
; | |
d3.select('#control').append('input') | |
.attr('id','selector_b') | |
.attr('class','selector B') | |
; | |
d3.select('#control').append('a') | |
.attr('id','enter') | |
.attr('class','button') | |
.text('Search') | |
; | |
d3.select('#control').append('br'); | |
d3.select('#control').append('a') | |
.attr('id','refresh') | |
.attr('class','button') | |
.html('Next') | |
; | |
// When the user selects an option, update the class for those features | |
d3.selectAll('.selector').on('change.query', updateQuery); | |
d3.selectAll('#enter').on('click', updateQuery); | |
d3.selectAll('#refresh').on('click', cycleQuery); | |
d3.select('#selector_a').node().value = G.input_A; | |
d3.select('#selector_b').node().value = G.input_B; | |
} | |
function escapeRegExp(stringToGoIntoTheRegex) { | |
return stringToGoIntoTheRegex.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment