Last active
October 22, 2015 19:14
-
-
Save mapsense-examples/8ad03c1400b93251f5e2 to your computer and use it in GitHub Desktop.
Paste addresses to (batch) geocode
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
<!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="mapsense.js" charset="utf-8"></script> | |
<link type="text/css" href="mapsense.css" rel="stylesheet"/> | |
<link rel="stylesheet" href="https://developer.mapsense.co/stylesheets/bootstrap.min.css" media="screen" type="text/css"> | |
<style> | |
html, body, #myMap{ | |
height: 100%; | |
width: 100%; | |
margin: 0; padding: 0; | |
font-family: sans-serif; | |
overflow: hidden; | |
} | |
#myMap { | |
position: absolute; | |
top: 0; right: 0; bottom: 0; left: 0; | |
} | |
path { | |
vector-effect: non-scaling-stroke; | |
stroke-linejoin: round; | |
stroke-linecap: round; | |
stroke: #777; | |
fill: none; | |
} | |
circle { | |
fill: rgba(68, 167, 228, 0.5); /*blue*/ | |
stroke: none; | |
} | |
.top-left { | |
position: absolute; | |
top: 5px; | |
left: 5px; | |
} | |
.btn { | |
background-color: rgba(255,255,255,0.8); | |
outline: 1px solid rgba(0,0,0,0.3); | |
min-width: 60px; | |
padding: 2px 4px; | |
margin: 2px; | |
display: inline-block; | |
} | |
.point_highlight { | |
fill: rgba(68, 167, 228, 0.5); | |
/*stroke: rgba(255, 116, 0,1); orange*/ | |
stroke: rgba(68, 167, 228, 1); /*blue*/ | |
stroke-width: 2; | |
radius: 50; | |
} | |
.park { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-attribution { | |
/* | |
*/ | |
background-color: rgba(250,250,250, 0.5); | |
color: #333; | |
position: absolute; | |
bottom: 0px; | |
right: 0px; | |
padding: 3px; | |
font-size: 13px; | |
font-family: sans-serif; | |
line-height: 15px; | |
text-decoration: none; | |
z-index: 99; | |
} | |
.mapsense-attribution a { | |
color: #333; | |
text-decoration: none; | |
} | |
/* | |
Non-map UI | |
*/ | |
#btn_group { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
} | |
.btn { | |
background-color: transparent; | |
outline: 1px solid rgba(0,0,0,0.3); | |
min-width: 60px; | |
padding: 2px 4px; | |
margin: 2px; | |
-webkit-transition: all 0.2s ease 0s; | |
-moz-transition: all 0.2s ease 0s; | |
-o-transition: all 0.2s ease 0s; | |
transition: all 0.2s ease 0s; | |
} | |
.btn:hover { | |
outline: 1px solid rgba(0,0,0,1); | |
background-color: rgba(255,255,255,0.8); | |
-webkit-transition: all 0.2s ease 0s; | |
-moz-transition: all 0.2s ease 0s; | |
-o-transition: all 0.2s ease 0s; | |
transition: all 0.2s ease 0s; | |
} | |
.smalllabel { | |
font-size: 12px; | |
line-height: 10px; | |
font-weight: 300; | |
display: inline-block; | |
margin: 0 auto; | |
/*float: right;*/ | |
} | |
#lede { | |
position: absolute; | |
top: 0; | |
left: 0; | |
padding: 5px 10px; | |
background: rgba(255,255,255,0.5); | |
width: 100%; | |
height: 20%; | |
max-height: 300px; | |
border: 0; | |
border-bottom: 1px solid #ddd; | |
} | |
#results { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
padding: 5px 10px; | |
background: rgba(255,255,255,0.5); | |
width: 100%; | |
height: 20%; | |
max-height: 300px; | |
white-space: pre; | |
overflow: scroll; | |
font-family: monospace; | |
border: 0; | |
} | |
.compass { | |
fill: none; | |
display:none; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="myMap"></div> | |
<!-- <textarea readonly="false" id="lede" placeholder="Paste addresses or names here, one per line. | |
466 Geary St, SF, CA | |
1600 Pennsylvania Ave | |
Tenderloin | |
nob hill | |
DC | |
mt everest | |
El Dorado | |
Atlantis | |
Valhalla"></textarea> | |
--> | |
<textarea id="lede" placeholder="Paste addresses or names here, one per line. Results below."></textarea> | |
<div id="btn_group"> | |
<button class="btn" id="go_geocode">GO!</button> | |
<p> | |
<div> | |
<label> | |
<input type="checkbox" id="viewbox"> | |
<span class="smalllabel"> | |
Within<br/>view | |
</span> | |
</label> | |
</div> | |
<div> | |
<label> | |
<input type="checkbox" id="show_imagery"> | |
<span class="smalllabel"> | |
Aerial | |
</span> | |
</label> | |
</div> | |
</p> | |
</div> | |
<div id="results" placeholder="results"> | |
</div> | |
<script src='http://code.jquery.com/jquery-1.11.0.min.js' type="text/javascript"></script> | |
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js' type="text/javascript"></script> | |
<script> | |
var my_key = "key-2d5eacd8b924489c8ed5e8418bd883bc"; | |
var us = [ // reverse of NESW | |
{lon: -124.85, lat: 24.40}, // west, south | |
{lon: -66.88, lat: 49.38} // east, north | |
]; | |
var home = [ | |
{lon: -130, lat: 20}, | |
{lon: -60, lat: 55} | |
]; | |
var result_fields = ['lat','lon','display_name','class','type','importance']; | |
var LINE_COUNT = 0; | |
var intr = mapsense.interact(); | |
var arro = mapsense.arrow(); | |
var map = mapsense.map('#myMap') // init the map | |
.extent(home) // zoom to bounds, regardless of window size | |
.tileSize({x:256,y:256}) | |
.add( | |
mapsense.basemap().apiKey(my_key).style("parchment") | |
) | |
; | |
var credit = '<a target="_blank" href="https://developer.mapsense.co/tileViewer/?tileset=mapsense.earth">©Mapsense ©OpenStreetMap</a> <a target="_blank" href="http://stamen.com">Stamen Design</a>'; | |
credit += '<br/>Nominatim Search Courtesy of <a href="http://www.mapquest.com/" target="_blank">MapQuest</a> <img src="http://developer.mapquest.com/content/osm/mq_logo.png">'; | |
d3.select('.mapsense-attribution').html(credit); | |
map.interact(false); | |
map.add(mapsense.drag()); | |
map.add(mapsense.wheel()); | |
map.add(mapsense.dblclick()); | |
map.add(mapsense.touch()); | |
mapsense.compass().map(map); //enable shift zoom | |
var max_extents = [ | |
{lon: 180, lat: 90}, // opposites, because we'll expand out | |
{lon: -180, lat: -90} | |
]; | |
satellite_url = "http://{S}.mqcdn.com/tiles/1.0.0/sat/{Z}/{X}/{Y}.jpg"; // Credit http://developer.mapquest.com/web/products/open/map | |
imagery_layer = mapsense.image() | |
.url(mapsense.url(satellite_url) | |
.hosts(["otile1", "otile2", "otile3", "otile4"])); | |
labels_url = "http://stamen-tiles-{S}.a.ssl.fastly.net/toner-labels/{Z}/{X}/{Y}.png"; | |
labels_layer = mapsense.image() | |
.url(mapsense.url(labels_url) | |
.hosts(["a", "b", "c", "d"])) | |
; | |
map.add(imagery_layer.visible(false).id("imagery_layer")); | |
map.add(labels_layer.visible(true).id("labels_layer")); | |
d3.select("#labels_layer").attr("style","opacity: 0.5;"); | |
function geocode(location, bbox){ | |
// Get lat/lon and bbox of search term, thanks to nominatim & mapquest. | |
//var geocode_url = "http://nominatim.openstreetmap.org/search?q=" + location + "&limit=1&format=json"; | |
var geocode_url = "http://open.mapquestapi.com/nominatim/v1/search.php?q=" + location + "&limit=1&format=json"; | |
//geocode_url += '&accept-language=en,en-US,en-GB,es'; | |
if (bbox) { | |
// left, top, right, bottom | |
geocode_url += '&viewbox=' + map.extent()[0].lon +','+ map.extent()[1].lat +','+ map.extent()[1].lon +','+ map.extent()[0].lat; | |
} | |
getResult(location, geocode_url); | |
} | |
function getResult(loc,geocode_url) { | |
$.ajax({ | |
dataType: "json", | |
url: geocode_url, | |
success: function(json) { | |
printResult(json, loc); | |
} | |
}); | |
} | |
function printResult(data, location) { | |
var lat, lon, gj_polygon, jsonbb, bbox, gj, bbox_km; | |
var previous_text, new_text, result_line, result_values; | |
if (data[0]) { | |
if (data[0].lat && data[0].lon) { | |
lat = data[0].lat; | |
lon = data[0].lon; | |
gj = ll2json(lat,lon,"your place"); | |
//if ( pt_layer && pt_layer.visible() ) { map.remove(pt_layer); } | |
var another_pt_layer = mapsense.geoJson() | |
.features(gj.features) | |
.selection(function(d){ | |
d.attr("class", "point_highlight") | |
.attr("r", "10") | |
; | |
}); | |
map.add(another_pt_layer); | |
previous_text = d3.select('#results').html(); | |
result_values = []; | |
for (var i = 0; i < result_fields.length; i++) { | |
result_values.push( data[0][result_fields[i]] ); | |
} | |
result_line = "<br/>" + location + '|' + result_values.join('|'); | |
new_text = previous_text + result_line; | |
d3.select('#results').html(new_text); | |
} | |
if (data[0].boundingbox) { | |
jsonbb = data[0].boundingbox; | |
bbox = [ | |
{lon: jsonbb[2], lat: jsonbb[0]}, | |
{lon: jsonbb[3], lat: jsonbb[1]} | |
]; | |
// If the size of the bbox is small, only zoom to 13ish | |
// Diagonal distance across bbox | |
bbox_km = llll2km(jsonbb[0],jsonbb[2],jsonbb[1],jsonbb[3]); | |
if (LINE_COUNT == 1) { | |
if (bbox_km > 10) { | |
map.extent(bbox); | |
} else { | |
map.center({lon: lon, lat: lat}) | |
.zoom(14); | |
} | |
} else { | |
updateMaxBbox(lat,lon); | |
} | |
} | |
} else { | |
//alert("No results found."); | |
//d3.select('#results').text("Not found: " + location); | |
previous_text = d3.select('#results').html(); | |
new_text = previous_text + "<br>Not found: " + location; | |
d3.select('#results').html(new_text); | |
} | |
} | |
function zoomBounds(e) { | |
/*map.extent(bounds(e.features)).zoomBy(-.1);*/ | |
map.extent(bounds(e.features)).zoom(14); | |
} | |
function ll2json(lat,lon,name){ | |
var gj = {type: "FeatureCollection", features: []}; //init a geojson object | |
var feature = { | |
type: "Feature", | |
geometry: {type: "Point", "coordinates": [ +lon, +lat ]}, | |
properties: { | |
name: name | |
} | |
}; | |
gj.features.push(feature); | |
return gj; | |
} | |
function llll2km(lat1,lon1,lat2,lon2) { | |
var R = 6371; // Radius of the earth in km | |
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 km | |
return d; | |
} | |
function deg2rad(deg) { | |
return deg * (Math.PI/180); | |
} | |
$(document).ready(function(){ | |
$("#results").click(function() { | |
selectText("results"); | |
}); | |
$("#go_geocode").click(function(e) { | |
var headline = 'INPUT|' + result_fields.join('|'); | |
d3.select('#results').html(headline); | |
var lines = $('#lede').val() || $('#lede').attr("placeholder"); | |
lines = lines.split(/\n/); | |
LINE_COUNT = lines.length; | |
var bbox = false; | |
if(document.getElementById('viewbox').checked) { | |
bbox = true; | |
} else { | |
bbox = false; | |
} | |
for (var i = 0; i < lines.length; i++) { | |
latlon = geocode( lines[i], bbox ); | |
} | |
}); | |
d3.select('#show_imagery').on("click",function() { | |
if ( document.getElementById('show_imagery').checked ) { | |
imagery_layer.visible(true); | |
} else { | |
imagery_layer.visible(false); | |
} | |
}); | |
}); | |
//--selectText grabs the full contents of a given container on click. | |
function selectText(containerid) { | |
var range; | |
if (document.selection) { | |
range = document.body.createTextRange(); | |
range.moveToElementText(document.getElementById(containerid)); | |
range.select(); | |
} else if (window.getSelection) { | |
range = document.createRange(); | |
range.selectNode(document.getElementById(containerid)); | |
window.getSelection().addRange(range); | |
} | |
} | |
function updateMaxBbox(y,x) { | |
y = parseFloat(y); | |
x = parseFloat(x); | |
if ( x < max_extents[0].lon ) { // more west than max west? | |
max_extents[0].lon = x; | |
} | |
if ( x > max_extents[1].lon ) { // more east than max east? | |
max_extents[1].lon = x; | |
} | |
if ( y < max_extents[0].lat ) { // more south than max south? | |
max_extents[0].lat = y; | |
} | |
if ( y > max_extents[1].lat ) { // more north than max north? | |
max_extents[1].lat = y; | |
} | |
map.extent(max_extents).zoomBy(-0.5); | |
/* | |
max_extents = [ | |
{lon: 180, lat: 90}, // west, south | |
{lon: -180, lat: -90} //east, north | |
]; | |
*/ | |
} | |
/*TODO: | |
labels | |
satellite | |
*/ | |
</script> | |
</body> | |
</html> |
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
.mapsense-attribution { | |
background-color: rgba(0, 0, 0, 0.5); | |
color: #ccc; | |
position: absolute; | |
bottom: 0; | |
right: 0; | |
padding: 10px; | |
font-size: 11px; | |
font-family: sans-serif; | |
line-height: 5px; | |
text-decoration: none; | |
} | |
.mapsense-attribution a { | |
color: #ccc; | |
text-decoration: none; | |
} | |
.compass .back { | |
fill: #eee; | |
fill-opacity: .8; | |
} | |
.compass .fore { | |
stroke: #999; | |
stroke-width: 1.5px; | |
} | |
.compass rect.back.fore { | |
fill: #999; | |
fill-opacity: .3; | |
stroke: #eee; | |
stroke-width: 1px; | |
shape-rendering: crispEdges; | |
} | |
.compass .direction { | |
fill: none; | |
} | |
.compass .chevron { | |
fill: none; | |
stroke: #999; | |
stroke-width: 5px; | |
} | |
.compass .zoom .chevron { | |
stroke-width: 4px; | |
} | |
.compass .active .chevron, | |
.compass .chevron.active { | |
stroke: #fff; | |
} | |
.compass.active .active .direction { | |
fill: #999; | |
} | |
path { | |
vector-effect: non-scaling-stroke; | |
stroke-linejoin: round; | |
stroke-linecap: round; | |
} | |
/* zoom-dependent styling for roads & admins */ | |
/* general rules */ | |
path._undefined.none { | |
fill: none; | |
} | |
path.landuse.other { | |
fill: none; | |
stroke: none; | |
} | |
.park { | |
opacity: .3; | |
} | |
.water_line { | |
opacity: .5; | |
fill: none; | |
} | |
.runway { | |
opacity: .7; | |
} | |
.rail_major, | |
.rail_minor { | |
stroke-dasharray: 4, 3; | |
} | |
.roads, | |
.admin { | |
fill: none; | |
} | |
.disputed_border, | |
.state_border { | |
stroke-dasharray: 8, 3; | |
} | |
/* admins */ | |
.country_border { | |
stroke-width: 2; | |
} | |
.admin._3, | |
.admin._2, | |
.admin._1 { | |
stroke-width: 1; | |
} | |
.admin._4 { | |
stroke-width: 1.5; | |
} | |
.state_border._2 { | |
stroke-width: .3; | |
} | |
.state_border._3, | |
.state_border._4 { | |
stroke-width: .5; | |
} | |
.state_border._8, | |
.state_border._9, | |
.state_border._10, | |
.state_border._11 { | |
stroke-width: 1.5; | |
opacity: .75; | |
} | |
.state_border._12, | |
.state_border._13, | |
.state_border._14, | |
.state_border._15, | |
.state_border._16, | |
.state_border._17 { | |
stroke-width: 2; | |
stroke-dasharray: 10, 4; | |
opacity: .75; | |
} | |
/* roads */ | |
.ne_10m_roads._3 { | |
stroke-width: 0.5; | |
} | |
.ne_10m_roads._4, | |
.ne_10m_roads._5 { | |
stroke-width: 0.75; | |
} | |
.ne_10m_roads._6 { | |
opacity: 0.75; | |
} | |
.motorway._6 { | |
stroke-width: .75; | |
} | |
.motorway._7, | |
.ne_10m_roads._7 { | |
stroke-width: 1; | |
} | |
.arterial_major._7 { | |
stroke-width: .5; | |
} | |
/* zoom 8 */ | |
.ne_10m_roads._8 { | |
stroke-width: 1.25; | |
opacity: 0.75; | |
} | |
.motorway._8 { | |
stroke-width: 1.25; | |
opacity: 0.75; | |
} | |
.arterial_major._8 { | |
stroke-width: .5; | |
opacity: .75; | |
} | |
.arterial_minor._8 { | |
stroke-width: 0.5; | |
opacity: .5; | |
} | |
/* zoom 9 */ | |
.motorway._9 { | |
stroke-width: 1.5; | |
} | |
.arterial_major._9 { | |
stroke-width: .75; | |
} | |
.arterial_minor._9 { | |
stroke-width: 0.5; | |
opacity: .5; | |
} | |
/* zoom 10 */ | |
.motorway._10 { | |
stroke-width: 1.75; | |
} | |
.arterial_major._10 { | |
stroke-width: 1.25; | |
} | |
.arterial_minor._10 { | |
stroke-width: 1; | |
} | |
.road_med._10 { | |
stroke-width: .75; | |
} | |
/* zoom 11 */ | |
.motorway._11 { | |
stroke-width: 3; | |
} | |
.arterial_major._11, | |
.runway._11 { | |
stroke-width: 1.75; | |
} | |
.arterial_minor._11 { | |
stroke-width: 1.25; | |
} | |
.road_med._11 { | |
stroke-width: 1; | |
} | |
.road_minor._11 { | |
stroke-width: .5; | |
} | |
/* zoom 12 */ | |
.motorway._12 { | |
stroke-width: 3.5; | |
} | |
.arterial_major._12, | |
.runway._12 { | |
stroke-width: 2; | |
} | |
.arterial_minor._12 { | |
stroke-width: 1.5; | |
} | |
.road_med._12 { | |
stroke-width: 1; | |
} | |
.road_minor._12 { | |
stroke-width: .75; | |
} | |
/* zoom 13 */ | |
.motorway._13 { | |
stroke-width: 5; | |
} | |
.arterial_major._13, | |
.runway._13 { | |
stroke-width: 3.5; | |
} | |
.arterial_minor._13 { | |
stroke-width: 2.5; | |
} | |
.road_med._13 { | |
stroke-width: 2; | |
} | |
.road_minor._13 { | |
stroke-width: 1.5; | |
} | |
.path._13, | |
.water_line._13 { | |
stroke-width: 0.3; | |
} | |
/* zoom 14 */ | |
.motorway._14 { | |
stroke-width: 6.5; | |
} | |
.arterial_major._14, | |
.runway._14 { | |
stroke-width: 4.75; | |
} | |
.arterial_minor._14 { | |
stroke-width: 4.25; | |
} | |
.road_med._14 { | |
stroke-width: 3.75; | |
} | |
.road_minor._14 { | |
stroke-width: 2.5; | |
} | |
.rail_minor._14 { | |
stroke-width: 1; | |
} | |
.rail_major._14 { | |
stroke-width: 1.5; | |
} | |
.path._14, | |
.water_line._14 { | |
stroke-width: 0.75; | |
} | |
/* zoom 15 */ | |
.motorway._15 { | |
stroke-width: 8.5; | |
} | |
.arterial_major._15, | |
.runway._15 { | |
stroke-width: 7; | |
} | |
.arterial_minor._15 { | |
stroke-width: 6; | |
} | |
.road_med._15 { | |
stroke-width: 5; | |
} | |
.road_minor._15 { | |
stroke-width: 4; | |
} | |
.rail_minor._15 { | |
stroke-width: 1.5; | |
} | |
.rail_major._15 { | |
stroke-width: 1.5; | |
} | |
.path._15, | |
.water_line._15 { | |
stroke-width: 1; | |
} | |
/* zoom 16 */ | |
.motorway._16 { | |
stroke-width: 12; | |
} | |
.arterial_major._16, | |
.runway._16 { | |
stroke-width: 9; | |
} | |
.arterial_minor._16 { | |
stroke-width: 8; | |
} | |
.road_med._16 { | |
stroke-width: 7; | |
} | |
.road_minor._16 { | |
stroke-width: 6; | |
} | |
.rail_minor._16 { | |
stroke-width: 1.5; | |
} | |
.rail_major._16 { | |
stroke-width: 2; | |
} | |
.path._16, | |
.water_line._16 { | |
stroke-width: 2; | |
} | |
/* zoom 17 */ | |
.motorway._17 { | |
stroke-width: 15; | |
} | |
.arterial_major._17, | |
.runway._17 { | |
stroke-width: 10; | |
} | |
.arterial_minor._17 { | |
stroke-width: 9; | |
} | |
.road_med._17 { | |
stroke-width: 8; | |
} | |
.road_minor._17 { | |
stroke-width: 8; | |
} | |
.rail_minor._17 { | |
stroke-width: 3; | |
stroke-dasharray: 7, 6; | |
} | |
.rail_major._17 { | |
stroke-width: 4; | |
stroke-dasharray: 7, 6; | |
} | |
.path._17, | |
.water_line._17 { | |
stroke-width: 5; | |
} | |
/* zoom 18 */ | |
.motorway._18 { | |
stroke-width: 16; | |
} | |
.arterial_major._18, | |
.runway._18 { | |
stroke-width: 10; | |
} | |
.arterial_minor._18 { | |
stroke-width: 10; | |
} | |
.road_med._18 { | |
stroke-width: 9; | |
} | |
.road_minor._18 { | |
stroke-width: 8; | |
} | |
.rail_minor._18 { | |
stroke-width: 3; | |
stroke-dasharray: 7, 6; | |
} | |
.rail_major._18 { | |
stroke-width: 4; | |
stroke-dasharray: 7, 6; | |
} | |
.path._18, | |
.water_line._18 { | |
stroke-width: 5; | |
} | |
.mapsense-blackprint.labels { | |
font-size: 18; | |
fill: #999; | |
font-weight: 200; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-blackprint.tile-background { | |
fill: #141414; | |
} | |
.mapsense-blackprint.land { | |
fill: #000000; | |
stroke: #bdbcbc; | |
stroke-width: 1; | |
} | |
.mapsense-blackprint.agriculture { | |
display: none; | |
} | |
.mapsense-blackprint.water_polygon { | |
fill: #141414; | |
} | |
.mapsense-blackprint.water_line { | |
stroke: #323232; | |
stroke-width: 1; | |
opacity: .3; | |
fill: none; | |
} | |
.mapsense-blackprint.park { | |
fill: rgba(15, 15, 15, 0.3); | |
stroke: none; | |
} | |
.mapsense-blackprint.building { | |
fill: none; | |
stroke: #505050; | |
stroke-width: .52; | |
} | |
.mapsense-blackprint.school { | |
fill: #0f0f0f; | |
stroke: none; | |
} | |
.mapsense-blackprint.other { | |
stroke: #323232; | |
} | |
.mapsense-blackprint.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-blackprint.roads { | |
stroke: #222; | |
} | |
.mapsense-blackprint.motorway, | |
.mapsense-blackprint.ne_10m_roads { | |
stroke: #333; | |
} | |
.mapsense-blackprint.country_border, | |
.mapsense-blackprint.disputed_border { | |
stroke: grey; | |
stroke-dasharray: 3 3; | |
stroke-width: 1; | |
fill: none; | |
} | |
.mapsense-blackprint.state_border { | |
stroke: grey; | |
stroke-dasharray: 3 3; | |
fill: none; | |
} | |
.mapsense-dark.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-dark.tile-background { | |
fill: #3a4250; | |
} | |
.mapsense-dark.land { | |
fill: #161619; | |
} | |
.mapsense-dark.agriculture { | |
display: none; | |
} | |
.mapsense-dark.country_border, | |
.mapsense-dark.disputed_border { | |
stroke: #777; | |
} | |
.mapsense-dark.state_border { | |
stroke: #777; | |
} | |
.mapsense-dark.water_polygon { | |
fill: #3a4250; | |
stroke: none; | |
} | |
.mapsense-dark.water_line { | |
stroke: #516283; | |
stroke-width: 1; | |
fill: none; | |
} | |
.mapsense-dark.park { | |
fill: #268061; | |
} | |
.mapsense-dark.building { | |
fill: #222; | |
stroke: black; | |
} | |
.mapsense-dark.school { | |
fill: #35161e; | |
} | |
.mapsense-dark.other { | |
fill: rgba(76, 69, 67, 0.15); | |
stroke: black; | |
} | |
.mapsense-dark.urban { | |
fill: #000000; | |
stroke: none; | |
} | |
.mapsense-dark.ne_10m_roads { | |
stroke: #7c5c2c; | |
} | |
.mapsense-dark.motorway { | |
stroke: #7c5c2c; | |
} | |
.mapsense-dark.arterial_major { | |
stroke: #4a472a; | |
} | |
.mapsense-dark.arterial_minor { | |
stroke: #3c3c3c; | |
} | |
.mapsense-dark.road_med { | |
stroke: #3c3c3c; | |
} | |
.mapsense-dark.road_minor { | |
stroke: #323232; | |
} | |
.mapsense-dark.rail_major { | |
stroke: #323228; | |
fill: none; | |
} | |
.mapsense-dark.rail_minor { | |
stroke: #323228; | |
fill: none; | |
} | |
.mapsense-dark.runway { | |
stroke: #333; | |
} | |
.mapsense-dark.path { | |
stroke: #323228; | |
} | |
.mapsense-grayscale.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-grayscale.tile-background { | |
fill: #aaa9af; | |
} | |
.mapsense-grayscale.land { | |
fill: #e8e5e5; | |
stroke: none; | |
} | |
.mapsense-grayscale.water_polygon { | |
fill: #aaa9af; | |
} | |
.mapsense-grayscale.water_line { | |
stroke: gray; | |
stroke-width: 1; | |
} | |
.mapsense-grayscale.park { | |
fill: #cad4ca; | |
} | |
.mapsense-grayscale.building { | |
fill: #f0f0f0; | |
} | |
.mapsense-grayscale.school { | |
fill: #dcdcdc; | |
stroke: none; | |
} | |
.mapsense-grayscale.other { | |
fill: #cbcbcb; | |
stroke: none; | |
} | |
.mapsense-grayscale.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-grayscale.ne_10m_roads { | |
stroke: #ccc; | |
} | |
.mapsense-grayscale.motorway { | |
stroke: #CCC; | |
} | |
.mapsense-grayscale.arterial_major { | |
stroke: #d2d2d2; | |
} | |
.mapsense-grayscale.arterial_minor { | |
stroke: #d7d7d7; | |
} | |
.mapsense-grayscale.road_med { | |
stroke: #DDD; | |
} | |
.mapsense-grayscale.road_minor { | |
stroke: #DDD; | |
} | |
.mapsense-grayscale.rail_major { | |
stroke: lightgray; | |
} | |
.mapsense-grayscale.rail_minor { | |
stroke: lightgray; | |
} | |
.mapsense-grayscale.runway { | |
stroke: #f2efef; | |
} | |
.mapsense-grayscale.path { | |
stroke: #DDD; | |
} | |
.mapsense-grayscale.country_border, | |
.mapsense-grayscale.disputed_border { | |
stroke: #BBB; | |
} | |
.mapsense-grayscale.state_border { | |
stroke: #bbb; | |
} | |
.mapsense-light.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-light.tile-background { | |
fill: #ffffff; | |
} | |
.mapsense-light.land { | |
fill: #ddddda; | |
stroke: none; | |
} | |
.mapsense-light.water_polygon { | |
fill: #ffffff; | |
stroke: none; | |
} | |
.mapsense-light.country_border, | |
.mapsense-light.disputed_border { | |
stroke: #aaa; | |
} | |
.mapsense-light.state_border { | |
stroke: #aaa; | |
} | |
.mapsense-light.water_line { | |
stroke: #c8d0df; | |
} | |
.mapsense-light.park { | |
fill: #caceb9; | |
stroke: none; | |
} | |
.mapsense-light.building { | |
fill: #d7d6d1; | |
} | |
.mapsense-light.school { | |
fill: #eadddb; | |
stroke: none; | |
} | |
.mapsense-light.other { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-light.urban { | |
fill: rgba(177, 135, 74, 0.07); | |
stroke: none; | |
} | |
.mapsense-light.ne_10m_roads { | |
stroke: #ededed; | |
} | |
.mapsense-light.motorway { | |
stroke: #f6f4f3; | |
} | |
.mapsense-light.arterial_major { | |
stroke: #f4f4ef; | |
} | |
.mapsense-light.arterial_minor { | |
stroke: #f1eded; | |
} | |
.mapsense-light.road_med { | |
stroke: #f6f3f3; | |
} | |
.mapsense-light.road_minor { | |
stroke: #faf8f8; | |
} | |
.mapsense-light.rail_major { | |
stroke: #cccccc; | |
} | |
.mapsense-light.rail_minor { | |
stroke: #cccccc; | |
} | |
.mapsense-light.runway { | |
stroke: #ccc; | |
} | |
.mapsense-light.path { | |
stroke: #ccc; | |
} | |
.mapsense-parchment.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-parchment.tile-background { | |
fill: #b8bdc1; | |
} | |
.mapsense-parchment.land { | |
fill: #e3d6c5; | |
stroke: #976f60; | |
stroke-width: 1; | |
} | |
.mapsense-parchment.water_polygon { | |
fill: #b8bdc1; | |
stroke: #976f60; | |
stroke-width: .5; | |
} | |
.mapsense-parchment.water_line { | |
stroke: #b8bdc1; | |
} | |
.mapsense-parchment.park { | |
fill: #cbbca6; | |
} | |
.mapsense-parchment.building { | |
fill: #d4c9ba; | |
} | |
.mapsense-parchment.school { | |
fill: rgba(90, 46, 5, 0.12); | |
stroke: none; | |
} | |
.mapsense-parchment.other { | |
fill: #e3d6c5; | |
stroke: none; | |
} | |
.mapsense-parchment.urban { | |
fill: rgba(255, 255, 255, 0.3); | |
stroke: none; | |
} | |
.mapsense-parchment.ne_10m_roads { | |
stroke: #aea295; | |
} | |
.mapsense-parchment.motorway { | |
stroke: #aea295; | |
} | |
.mapsense-parchment.arterial_major { | |
stroke: #d5c5b7; | |
} | |
.mapsense-parchment.arterial_minor { | |
stroke: #cab7aa; | |
} | |
.mapsense-parchment.road_med { | |
stroke: #d8c8bb; | |
} | |
.mapsense-parchment.road_minor { | |
stroke: #d8c8bb; | |
} | |
.mapsense-parchment.rail_major { | |
stroke: #bda897; | |
} | |
.mapsense-parchment.rail_minor { | |
stroke: #bda897; | |
} | |
.mapsense-parchment.runway { | |
stroke: #ede5d5; | |
} | |
.mapsense-parchment.path { | |
stroke: #ede5d5; | |
} | |
.mapsense-parchment.country_border, | |
.mapsense-parchment.disputed_border { | |
stroke: #976f60; | |
} | |
.mapsense-parchment.state_border { | |
stroke: #976f60; | |
} | |
.mapsense-sketch.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-sketch.tile-background { | |
fill: #f0f0f0; | |
} | |
.mapsense-sketch.land { | |
fill: #ffffff; | |
stroke: #bdbcbc; | |
stroke-width: 1; | |
} | |
.mapsense-sketch.water_polygon { | |
fill: #f0f0f0; | |
stroke: #bdbcbc; | |
stroke-width: .5; | |
} | |
.mapsense-sketch.water_line { | |
stroke: #cccaca; | |
} | |
.mapsense-sketch.park { | |
fill: none; | |
stroke-width: .55; | |
stroke: #dad8d8; | |
} | |
.mapsense-sketch.building { | |
fill: none; | |
stroke: #d4d4d4; | |
stroke-width: .52; | |
} | |
.mapsense-sketch.school { | |
fill: none; | |
stroke: #dadada; | |
} | |
.mapsense-sketch.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-sketch.ne_10m_roads { | |
stroke: #eee; | |
} | |
.mapsense-sketch.motorway { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.arterial_major { | |
stroke: #eee; | |
} | |
.mapsense-sketch.arterial_minor { | |
stroke: #eee; | |
} | |
.mapsense-sketch.road_med { | |
stroke: #eee; | |
} | |
.mapsense-sketch.road_minor { | |
stroke: #f5f5f5; | |
} | |
.mapsense-sketch.rail_major { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.rail_minor { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.runway { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.path { | |
stroke: #f5f5f5; | |
} | |
.mapsense-sketch.country_border, | |
.mapsense-sketch.disputed_border { | |
stroke: #ccc; | |
} | |
.mapsense-sketch.state_border { | |
stroke: #ccc; | |
} | |
.mapsense-tron.labels { | |
font-size: 14; | |
fill: #888; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-tron.tile-background { | |
fill: #000000; | |
} | |
.mapsense-tron.land { | |
fill: #172c3a; | |
stroke: none; | |
} | |
.mapsense-tron.water_polygon { | |
stroke: none; | |
fill: black; | |
} | |
.mapsense-tron.country_border, | |
.mapsense-tron.disputed_border { | |
stroke: #666; | |
} | |
.mapsense-tron.state_border { | |
stroke: #666; | |
} | |
.mapsense-tron.water_line { | |
stroke: #111119; | |
} | |
.mapsense-tron.park { | |
fill: #060a10; | |
} | |
.mapsense-tron.building { | |
fill: #07212a; | |
stroke: none; | |
} | |
.mapsense-tron.school { | |
fill: #162532; | |
stroke: none; | |
} | |
.mapsense-tron.other { | |
fill: #11242d; | |
stroke: none; | |
} | |
.mapsense-tron.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-tron.ne_10m_roads { | |
stroke: #36606f; | |
} | |
.mapsense-tron.motorway { | |
stroke: #36606f; | |
} | |
.mapsense-tron.arterial_major { | |
stroke: #295563; | |
} | |
.mapsense-tron.arterial_minor { | |
stroke: #295563; | |
} | |
.mapsense-tron.road_med { | |
stroke: #2b3e49; | |
} | |
.mapsense-tron.road_minor { | |
stroke: #1b333a; | |
} | |
.mapsense-tron.rail_major { | |
stroke: #1b333a; | |
} | |
.mapsense-tron.rail_minor { | |
stroke: #1b333a; | |
} | |
.mapsense-tron.runway { | |
stroke: #1e414c; | |
} | |
.mapsense-tron.path { | |
stroke: #1b333a; | |
} | |
.mapsense-vintage.labels { | |
font-size: 16; | |
fill: #4c83b2; | |
font-weight: 400; | |
text-transform: uppercase; | |
stroke-width: .3; | |
stroke: grey; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-vintage.tile-background { | |
fill: #87c8ca; | |
} | |
.mapsense-vintage.land { | |
fill: #fffaf2; | |
} | |
.mapsense-vintage.water_polygon { | |
fill: #87c8ca; | |
} | |
.mapsense-vintage.country_border, | |
.mapsense-vintage.disputed_border { | |
stroke: #aaa; | |
} | |
.mapsense-vintage.state_border { | |
stroke: #aaa; | |
} | |
.mapsense-vintage.water_line { | |
stroke: #b5e2e4; | |
} | |
.mapsense-vintage.park { | |
fill: #c6f3bd; | |
stroke: none; | |
} | |
.mapsense-vintage.building { | |
fill: #f9ece2; | |
stroke: none; | |
} | |
.mapsense-vintage.school { | |
fill: #f0eced; | |
stroke: none; | |
} | |
.mapsense-vintage.urban { | |
fill: rgba(243, 210, 191, 0.19); | |
stroke: none; | |
} | |
.mapsense-vintage.ne_10m_roads { | |
stroke: #ffb67e; | |
} | |
.mapsense-vintage.motorway { | |
stroke: #f6bd86; | |
} | |
.mapsense-vintage.arterial_major { | |
stroke: #c2d7df; | |
} | |
.mapsense-vintage.arterial_minor { | |
stroke: #cfdddb; | |
} | |
.mapsense-vintage.road_med { | |
stroke: #dae9ea; | |
} | |
.mapsense-vintage.road_minor { | |
stroke: #ededed; | |
} | |
.mapsense-vintage.rail_major { | |
stroke: #c7c4c4; | |
} | |
.mapsense-vintage.rail_minor { | |
stroke: #c7c4c4; | |
} | |
.mapsense-vintage.runway { | |
stroke: #e1dede; | |
} | |
.mapsense-vintage.path { | |
stroke: #e1dede; | |
} | |
.mapsense-simple.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-simple.tile-background { | |
fill: #CBE6F3; | |
} | |
.mapsense-simple.land { | |
fill: #ffffff; | |
stroke: #a2d3f2; | |
stroke-width: 1; | |
} | |
.mapsense-simple.water_polygon { | |
fill: #CBE6F3; | |
stroke: #b6d3e0; | |
stroke-width: 0.5px; | |
} | |
.mapsense-simple.water_line { | |
stroke: #a2d3f2; | |
} | |
.mapsense-simple.park { | |
fill: none; | |
stroke-width: 0; | |
stroke: #dad8d8; | |
} | |
.mapsense-simple.landuse { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.building { | |
fill: none; | |
stroke: #d4d4d4; | |
stroke-width: .52; | |
} | |
.mapsense-simple.school { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.other { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.ne_10m_roads { | |
stroke: #eee; | |
} | |
.mapsense-simple.motorway { | |
stroke: #ddd; | |
} | |
.mapsense-simple.arterial_major { | |
stroke: #eee; | |
} | |
.mapsense-simple.arterial_minor { | |
stroke: #eee; | |
} | |
.mapsense-simple.road_med { | |
stroke: #eee; | |
} | |
.mapsense-simple.road_minor { | |
stroke: #f5f5f5; | |
} | |
.mapsense-simple.rail_major { | |
stroke: #ddd; | |
} | |
.mapsense-simple.rail_minor { | |
stroke: #ddd; | |
} | |
.mapsense-simple.runway { | |
stroke: #ddd; | |
} | |
.mapsense-simple.path { | |
stroke: #f5f5f5; | |
} | |
.mapsense-simple.country_border, | |
.mapsense-simple.disputed_border { | |
stroke: #ccc; | |
} | |
.mapsense-simple.state_border { | |
stroke: #ccc; | |
} | |
.mapsense-simple.ne_10m_roads._3 { | |
stroke-width: 0.5; | |
stroke: none; | |
} | |
.mapsense-simple.ne_10m_roads._4, | |
.mapsense-simple.ne_10m_roads._5 { | |
stroke-width: 0.75; | |
stroke: none; | |
} | |
.mapsense-simple.ne_10m_roads._6 { | |
opacity: 0.75; | |
stroke: none; | |
} | |
svg.mapsense-map { | |
width: 100%; | |
height: 100%; | |
} |
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
(function(){ | |
var mapsense = {version: "1.0.0"}, | |
ms = mapsense; | |
var zero = {x: 0, y: 0}; | |
ms.ns = { | |
svg: "http://www.w3.org/2000/svg", | |
xlink: "http://www.w3.org/1999/xlink" | |
}; | |
function ns(name) { | |
var i = name.indexOf(":"); | |
return i < 0 ? name : { | |
space: ms.ns[name.substring(0, i)], | |
local: name.substring(i + 1) | |
}; | |
} | |
ms.id = (function() { | |
var id = 0; | |
return function() { | |
return ++id; | |
}; | |
})(); | |
ms.svg = function(type) { | |
return document.createElementNS(ms.ns.svg, type); | |
}; | |
ms.transform = function(a, b, c, d, e, f) { | |
var transform = {}, | |
zoomDelta, | |
zoomFraction, | |
k; | |
if (!arguments.length) { | |
a = 1; c = 0; e = 0; | |
b = 0; d = 1; f = 0; | |
} | |
transform.zoomFraction = function(x) { | |
if (!arguments.length) return zoomFraction; | |
zoomFraction = x; | |
zoomDelta = Math.floor(zoomFraction + Math.log(Math.sqrt(a * a + b * b + c * c + d * d)) / Math.LN2); | |
k = Math.pow(2, -zoomDelta); | |
return transform; | |
}; | |
transform.apply = function(x) { | |
var k0 = Math.pow(2, -x.zoom), | |
k1 = Math.pow(2, x.zoom - zoomDelta); | |
return { | |
column: (a * x.column * k0 + c * x.row * k0 + e) * k1, | |
row: (b * x.column * k0 + d * x.row * k0 + f) * k1, | |
zoom: x.zoom - zoomDelta | |
}; | |
}; | |
transform.unapply = function(x) { | |
var k0 = Math.pow(2, -x.zoom), | |
k1 = Math.pow(2, x.zoom + zoomDelta); | |
return { | |
column: (x.column * k0 * d - x.row * k0 * c - e * d + f * c) / (a * d - b * c) * k1, | |
row: (x.column * k0 * b - x.row * k0 * a - e * b + f * a) / (c * b - d * a) * k1, | |
zoom: x.zoom + zoomDelta | |
}; | |
}; | |
transform.toString = function() { | |
return "matrix(" + [a * k, b * k, c * k, d * k].join(" ") + " 0 0)"; | |
}; | |
return transform.zoomFraction(0); | |
}; | |
ms.cache = function(load, unload) { | |
var cache = {}, | |
locks = {}, | |
map = {}, | |
head = null, | |
tail = null, | |
size = 64, | |
n = 0; | |
function remove(tile) { | |
n--; | |
if (unload) unload(tile); | |
delete map[tile.key]; | |
if (tile.next) tile.next.prev = tile.prev; | |
else if (tail = tile.prev) tail.next = null; | |
if (tile.prev) tile.prev.next = tile.next; | |
else if (head = tile.next) head.prev = null; | |
} | |
function flush() { | |
for (var tile = tail; n > size; tile = tile.prev) { | |
if (!tile) break; | |
if (tile.lock) continue; | |
remove(tile); | |
} | |
} | |
cache.peek = function(c) { | |
return map[[c.zoom, c.column, c.row].join("/")]; | |
}; | |
cache.load = function(c, projection) { | |
var key = [c.zoom, c.column, c.row].join("/"), | |
tile = map[key]; | |
if (tile) { | |
if (tile.prev) { | |
tile.prev.next = tile.next; | |
if (tile.next) tile.next.prev = tile.prev; | |
else tail = tile.prev; | |
tile.prev = null; | |
tile.next = head; | |
head.prev = tile; | |
head = tile; | |
} | |
tile.lock = 1; | |
locks[key] = tile; | |
return tile; | |
} | |
tile = { | |
key: key, | |
column: c.column, | |
row: c.row, | |
zoom: c.zoom, | |
next: head, | |
prev: null, | |
lock: 1 | |
}; | |
load.call(null, tile, projection); | |
locks[key] = map[key] = tile; | |
if (head) head.prev = tile; | |
else tail = tile; | |
head = tile; | |
n++; | |
return tile; | |
}; | |
cache.unload = function(key) { | |
if (!(key in locks)) return false; | |
var tile = locks[key]; | |
tile.lock = 0; | |
delete locks[key]; | |
if (tile.request && tile.request.abort(false)) remove(tile); | |
return tile; | |
}; | |
cache.locks = function() { | |
return locks; | |
}; | |
cache.size = function(x) { | |
if (!arguments.length) return size; | |
size = x; | |
flush(); | |
return cache; | |
}; | |
cache.flush = function() { | |
flush(); | |
return cache; | |
}; | |
cache.clear = function() { | |
for (var key in map) { | |
var tile = map[key]; | |
if (tile.request) tile.request.abort(false); | |
if (unload) unload(map[key]); | |
if (tile.lock) { | |
tile.lock = 0; | |
tile.element.parentNode.removeChild(tile.element); | |
} | |
} | |
locks = {}; | |
map = {}; | |
head = tail = null; | |
n = 0; | |
return cache; | |
}; | |
return cache; | |
}; | |
ms.url = function(template) { | |
var hosts = [], | |
repeat = true; | |
function format(c) { | |
var max = c.zoom < 0 ? 1 : 1 << c.zoom, | |
column = c.column; | |
if (repeat) { | |
column = c.column % max; | |
if (column < 0) column += max; | |
} else if ((column < 0) || (column >= max)) { | |
return null; | |
} | |
return (typeof template === "function" ? template(c) : template).replace(/{(.)}/g, function(s, v) { | |
switch (v) { | |
case "S": return hosts[(Math.abs(c.zoom) + c.row + column) % hosts.length]; | |
case "Z": return c.zoom; | |
case "X": return column; | |
case "Y": return c.row; | |
case "B": { | |
var nw = ms.map.coordinateLocation({row: c.row, column: column, zoom: c.zoom}), | |
se = ms.map.coordinateLocation({row: c.row + 1, column: column + 1, zoom: c.zoom}), | |
pn = Math.ceil(Math.log(c.zoom) / Math.LN2); | |
return se.lat.toFixed(pn) + | |
"," + nw.lon.toFixed(pn) + | |
"," + nw.lat.toFixed(pn) + | |
"," + se.lon.toFixed(pn); | |
} | |
} | |
return v; | |
}); | |
} | |
format.template = function(x) { | |
if (!arguments.length) return template; | |
template = x; | |
return format; | |
}; | |
format.hosts = function(x) { | |
if (!arguments.length) return hosts; | |
hosts = x; | |
return format; | |
}; | |
format.repeat = function(x) { | |
if (!arguments.length) return repeat; | |
repeat = x; | |
return format; | |
}; | |
return format; | |
}; | |
ms.dispatch = function(that) { | |
var types = {}; | |
that.on = function(type, handler) { | |
var listeners = types[type] || (types[type] = []); | |
for (var i = 0; i < listeners.length; i++) { | |
if (listeners[i].handler == handler) return that; // already registered | |
} | |
listeners.push({handler: handler, on: true}); | |
return that; | |
}; | |
that.off = function(type, handler) { | |
var listeners = types[type]; | |
if (listeners) for (var i = 0; i < listeners.length; i++) { | |
var l = listeners[i]; | |
if (l.handler == handler) { | |
l.on = false; | |
listeners.splice(i, 1); | |
break; | |
} | |
} | |
return that; | |
}; | |
return function(event) { | |
var listeners = types[event.type]; | |
if (!listeners) return; | |
listeners = listeners.slice(); // defensive copy | |
for (var i = 0; i < listeners.length; i++) { | |
var l = listeners[i]; | |
if (l.on) l.handler.call(that, event); | |
} | |
}; | |
}; | |
ms.queue = (function() { | |
var queued = [], active = 0, size = 6; | |
function process() { | |
if ((active >= size) || !queued.length) return; | |
active++; | |
queued.pop()(); | |
} | |
function dequeue(send) { | |
for (var i = 0; i < queued.length; i++) { | |
if (queued[i] == send) { | |
queued.splice(i, 1); | |
return true; | |
} | |
} | |
return false; | |
} | |
function merge(dest, src) { | |
for (var property in src) { | |
dest[property] = src[property]; | |
} | |
return dest; | |
} | |
function request(url, callback, options) { | |
var req; | |
function send() { | |
req = new XMLHttpRequest(); | |
req.open("GET", url, true); | |
if (options) { | |
if (options.mimeType && req.overrideMimeType) | |
req.overrideMimeType(options.mimeType); | |
if (options.responseType) | |
req.responseType = options.responseType; | |
if (options.xhrFields) { | |
for (var f in options.xhrFields) { | |
req[f] = options.xhrFields[f]; | |
} | |
} | |
} | |
req.onreadystatechange = function(e) { | |
if (req.readyState == 4) { | |
active--; | |
if (req.status < 300) callback(req); | |
process(); | |
} | |
}; | |
req.send(null); | |
} | |
function abort(hard) { | |
if (dequeue(send)) return true; | |
if (hard && req) { req.abort(); return true; } | |
return false; | |
} | |
queued.push(send); | |
process(); | |
return {abort: abort}; | |
} | |
function text(url, callback, mimeType) { | |
return request(url, function(req) { | |
if (req.responseText) callback(req.responseText); | |
}, { mimeType: mimeType }); | |
} | |
/* | |
* We the override MIME type here so that you can load local files; some | |
* browsers don't assign a proper MIME type for local files. | |
*/ | |
function json(url, callback, options) { | |
return request(url, function(req) { | |
if (req.responseText) callback(JSON.parse(req.responseText)); | |
}, merge({ mimeType: "application/json" }, options)); | |
} | |
function xml(url, callback, options) { | |
return request(url, function(req) { | |
if (req.responseXML) callback(req.responseXML); | |
}, merge({ mimeType: "application/xml" }, options)); | |
} | |
function octetStream(url, callback, options) { | |
var defaultOptions = { | |
mimeType: "application/octet-stream", | |
responseType: "arraybuffer" | |
}; | |
return request(url, function(req) { | |
if (req.response) callback(req.response); | |
}, merge(defaultOptions, options)); | |
} | |
function image(imageElement, src, callback) { | |
var img; | |
function send() { | |
img = document.createElement("img"); | |
img.onerror = function() { | |
active--; | |
process(); | |
}; | |
img.onload = function() { | |
active--; | |
callback(img); | |
process(); | |
}; | |
img.src = src; | |
imageElement.setAttributeNS(ms.ns.xlink, "href", src); | |
} | |
function abort(hard) { | |
if (dequeue(send)) return true; | |
if (hard && img) { img.src = "about:"; return true; } // cancels request | |
return false; | |
} | |
queued.push(send); | |
process(); | |
return {abort: abort}; | |
} | |
return { | |
text: text, | |
xml: xml, | |
json: json, | |
octetStream: octetStream, | |
image: image | |
}; | |
})(); | |
ms.map = function(mapContainer) { | |
var map = {}, | |
container, | |
size, | |
sizeActual = zero, | |
sizeRadius = zero, // sizeActual / 2 | |
tileSize = {x: 512, y: 512}, | |
center = {lat: 37.76487, lon: -122.41948}, | |
zoom = 12, | |
zoomFraction = 0, | |
zoomFactor = 1, // Math.pow(2, zoomFraction) | |
zoomRange = [1, 18], | |
angle = 0, | |
angleCos = 1, // Math.cos(angle) | |
angleSin = 0, // Math.sin(angle) | |
angleCosi = 1, // Math.cos(-angle) | |
angleSini = 0, // Math.sin(-angle) | |
ymin = -180, // lat2y(centerRange[0].lat) | |
ymax = 180; // lat2y(centerRange[1].lat) | |
var centerRange = [ | |
{lat: y2lat(ymin), lon: -Infinity}, | |
{lat: y2lat(ymax), lon: Infinity} | |
]; | |
var interact = ms.interact(); | |
if (typeof mapContainer === "string") | |
container = document.querySelector(mapContainer); | |
else | |
container = mapContainer; | |
if (!container) | |
throw new Error("Invalid map container."); | |
map.interact = function(x) { | |
if (!arguments.length) return interact; | |
interact.map(x ? map : null); | |
}; | |
map.locationCoordinate = function(l) { | |
var c = ms.map.locationCoordinate(l), | |
k = Math.pow(2, zoom); | |
c.column *= k; | |
c.row *= k; | |
c.zoom += zoom; | |
return c; | |
}; | |
map.coordinateLocation = ms.map.coordinateLocation; | |
map.coordinatePoint = function(tileCenter, c) { | |
var kc = Math.pow(2, zoom - c.zoom), | |
kt = Math.pow(2, zoom - tileCenter.zoom), | |
dx = (c.column * kc - tileCenter.column * kt) * tileSize.x * zoomFactor, | |
dy = (c.row * kc - tileCenter.row * kt) * tileSize.y * zoomFactor; | |
return { | |
x: sizeRadius.x + angleCos * dx - angleSin * dy, | |
y: sizeRadius.y + angleSin * dx + angleCos * dy | |
}; | |
}; | |
map.pointCoordinate = function(tileCenter, p) { | |
var kt = Math.pow(2, zoom - tileCenter.zoom), | |
dx = (p.x - sizeRadius.x) / zoomFactor, | |
dy = (p.y - sizeRadius.y) / zoomFactor; | |
return { | |
column: tileCenter.column * kt + (angleCosi * dx - angleSini * dy) / tileSize.x, | |
row: tileCenter.row * kt + (angleSini * dx + angleCosi * dy) / tileSize.y, | |
zoom: zoom | |
}; | |
}; | |
map.locationPoint = function(l) { | |
var k = Math.pow(2, zoom + zoomFraction - 3) / 45, | |
dx = (l.lon - center.lon) * k * tileSize.x, | |
dy = (lat2y(center.lat) - lat2y(l.lat)) * k * tileSize.y; | |
return { | |
x: sizeRadius.x + angleCos * dx - angleSin * dy, | |
y: sizeRadius.y + angleSin * dx + angleCos * dy | |
}; | |
}; | |
map.pointLocation = function(p) { | |
var k = 45 / Math.pow(2, zoom + zoomFraction - 3), | |
dx = (p.x - sizeRadius.x) * k, | |
dy = (p.y - sizeRadius.y) * k; | |
return { | |
lon: center.lon + (angleCosi * dx - angleSini * dy) / tileSize.x, | |
lat: y2lat(lat2y(center.lat) - (angleSini * dx + angleCosi * dy) / tileSize.y) | |
}; | |
}; | |
function rezoom() { | |
if (zoomRange) { | |
if (zoom < zoomRange[0]) zoom = zoomRange[0]; | |
else if (zoom > zoomRange[1]) zoom = zoomRange[1]; | |
} | |
zoomFraction = zoom - (zoom = Math.round(zoom)); | |
zoomFactor = Math.pow(2, zoomFraction); | |
} | |
function recenter() { | |
if (!centerRange) return; | |
var k = 45 / Math.pow(2, zoom + zoomFraction - 3); | |
// constrain latitude | |
var y = Math.max(Math.abs(angleSin * sizeRadius.x + angleCos * sizeRadius.y), | |
Math.abs(angleSini * sizeRadius.x + angleCosi * sizeRadius.y)), | |
lat0 = y2lat(ymin - y * k / tileSize.y), | |
lat1 = y2lat(ymax + y * k / tileSize.y); | |
center.lat = Math.max(lat0, Math.min(lat1, center.lat)); | |
// constrain longitude | |
var x = Math.max(Math.abs(angleSin * sizeRadius.y + angleCos * sizeRadius.x), | |
Math.abs(angleSini * sizeRadius.y + angleCosi * sizeRadius.x)), | |
lon0 = centerRange[0].lon - x * k / tileSize.x, | |
lon1 = centerRange[1].lon + x * k / tileSize.x; | |
center.lon = Math.max(lon0, Math.min(lon1, center.lon)); | |
} | |
// a place to capture mouse events if no tiles exist | |
var rect = ms.svg("rect"); | |
rect.setAttribute("visibility", "hidden"); | |
rect.setAttribute("pointer-events", "all"); | |
var svgContainer = ms.svg("svg"); | |
svgContainer.setAttribute("class", "mapsense-map"); | |
svgContainer.appendChild(rect); | |
var relativeContainer = document.createElement("div"); | |
relativeContainer.style.setProperty("position", "relative"); | |
relativeContainer.style.setProperty("width", "100%"); | |
relativeContainer.style.setProperty("height", "100%"); | |
relativeContainer.appendChild(svgContainer); | |
container.appendChild(relativeContainer); | |
map.container = function() { | |
return container; | |
}; | |
map.relativeContainer = function() { | |
return relativeContainer; | |
}; | |
map.svgContainer = function() { | |
return svgContainer; | |
}; | |
map.focusableParent = function() { | |
for (var p = container; p; p = p.parentNode) { | |
if (p.tabIndex >= 0) return p; | |
} | |
return window; | |
}; | |
map.mouse = function(e) { | |
var point = (svgContainer.ownerSVGElement || svgContainer).createSVGPoint(); | |
if ((bug44083 < 0) && (window.scrollX || window.scrollY)) { | |
var svg = document.body.appendChild(ms.svg("svg")); | |
svg.style.position = "absolute"; | |
svg.style.top = svg.style.left = "0px"; | |
var ctm = svg.getScreenCTM(); | |
bug44083 = !(ctm.f || ctm.e); | |
document.body.removeChild(svg); | |
} | |
if (bug44083) { | |
point.x = e.pageX; | |
point.y = e.pageY; | |
} else { | |
point.x = e.clientX; | |
point.y = e.clientY; | |
} | |
return point.matrixTransform(svgContainer.getScreenCTM().inverse()); | |
}; | |
map.size = function(x) { | |
if (!arguments.length) return sizeActual; | |
size = x; | |
return map.resize(); // size tiles | |
}; | |
map.resize = function() { | |
if (!size) { | |
rect.setAttribute("width", "100%"); | |
rect.setAttribute("height", "100%"); | |
var b = rect.getBBox(); | |
sizeActual = {x: b.width, y: b.height}; | |
resizer.add(map); | |
} else { | |
sizeActual = size; | |
resizer.remove(map); | |
} | |
rect.setAttribute("width", sizeActual.x); | |
rect.setAttribute("height", sizeActual.y); | |
sizeRadius = {x: sizeActual.x / 2, y: sizeActual.y / 2}; | |
recenter(); | |
map.dispatch({type: "resize"}); | |
return map; | |
}; | |
map.tileSize = function(x) { | |
if (!arguments.length) return tileSize; | |
tileSize = x; | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.center = function(x) { | |
if (!arguments.length) return center; | |
center = x; | |
recenter(); | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.panBy = function(x) { | |
var k = 45 / Math.pow(2, zoom + zoomFraction - 3), | |
dx = x.x * k, | |
dy = x.y * k; | |
return map.center({ | |
lon: center.lon + (angleSini * dy - angleCosi * dx) / tileSize.x, | |
lat: y2lat(lat2y(center.lat) + (angleSini * dx + angleCosi * dy) / tileSize.y) | |
}); | |
}; | |
map.centerRange = function(x) { | |
if (!arguments.length) return centerRange; | |
centerRange = x; | |
if (centerRange) { | |
ymin = centerRange[0].lat > -90 ? lat2y(centerRange[0].lat) : -Infinity; | |
ymax = centerRange[0].lat < 90 ? lat2y(centerRange[1].lat) : Infinity; | |
} else { | |
ymin = -Infinity; | |
ymax = Infinity; | |
} | |
recenter(); | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.zoom = function(x) { | |
if (!arguments.length) return zoom + zoomFraction; | |
zoom = x; | |
rezoom(); | |
return map.center(center); | |
}; | |
map.zoomBy = function(z, x0, l) { | |
if (arguments.length < 2) return map.zoom(zoom + zoomFraction + z); | |
// compute the location of x0 | |
if (arguments.length < 3) l = map.pointLocation(x0); | |
// update the zoom level | |
zoom = zoom + zoomFraction + z; | |
rezoom(); | |
// compute the new point of the location | |
var x1 = map.locationPoint(l); | |
return map.panBy({x: x0.x - x1.x, y: x0.y - x1.y}); | |
}; | |
map.zoomRange = function(x) { | |
if (!arguments.length) return zoomRange; | |
zoomRange = x; | |
return map.zoom(zoom + zoomFraction); | |
}; | |
map.extent = function(x) { | |
if (!arguments.length) return [ | |
map.pointLocation({x: 0, y: sizeActual.y}), | |
map.pointLocation({x: sizeActual.x, y: 0}) | |
]; | |
// compute the extent in points, scale factor, and center | |
var bl = map.locationPoint(x[0]), | |
tr = map.locationPoint(x[1]), | |
k = Math.max((tr.x - bl.x) / sizeActual.x, (bl.y - tr.y) / sizeActual.y), | |
l = map.pointLocation({x: (bl.x + tr.x) / 2, y: (bl.y + tr.y) / 2}); | |
// update the zoom level | |
zoom = zoom + zoomFraction - Math.log(k) / Math.LN2; | |
rezoom(); | |
// set the new center | |
return map.center(l); | |
}; | |
map.angle = function(x) { | |
if (!arguments.length) return angle; | |
angle = x; | |
angleCos = Math.cos(angle); | |
angleSin = Math.sin(angle); | |
angleCosi = Math.cos(-angle); | |
angleSini = Math.sin(-angle); | |
recenter(); | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.add = function(x) { | |
x.map(map); | |
return map; | |
}; | |
map.remove = function(x) { | |
x.map(null); | |
return map; | |
}; | |
map.dispatch = ms.dispatch(map); | |
map.interact(true); | |
return map.resize(); // infer size | |
}; | |
function resizer(e) { | |
for (var i = 0; i < resizer.maps.length; i++) { | |
resizer.maps[i].resize(); | |
} | |
} | |
resizer.maps = []; | |
resizer.add = function(map) { | |
for (var i = 0; i < resizer.maps.length; i++) { | |
if (resizer.maps[i] == map) return; | |
} | |
resizer.maps.push(map); | |
}; | |
resizer.remove = function(map) { | |
for (var i = 0; i < resizer.maps.length; i++) { | |
if (resizer.maps[i] == map) { | |
resizer.maps.splice(i, 1); | |
return; | |
} | |
} | |
}; | |
// Note: assumes single window (no frames, iframes, etc.)! | |
if (window.addEventListener) { | |
window.addEventListener("resize", resizer, false); | |
window.addEventListener("load", resizer, false); | |
} | |
// See http://wiki.openstreetmap.org/wiki/Mercator | |
function y2lat(y) { | |
return 360 / Math.PI * Math.atan(Math.exp(y * Math.PI / 180)) - 90; | |
} | |
function lat2y(lat) { | |
return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)); | |
} | |
ms.map.locationCoordinate = function(l) { | |
var k = 1 / 360; | |
return { | |
column: (l.lon + 180) * k, | |
row: (180 - lat2y(l.lat)) * k, | |
zoom: 0 | |
}; | |
}; | |
ms.map.coordinateLocation = function(c) { | |
var k = 45 / Math.pow(2, c.zoom - 3); | |
return { | |
lon: k * c.column - 180, | |
lat: y2lat(180 - k * c.row) | |
}; | |
}; | |
// https://bugs.webkit.org/show_bug.cgi?id=44083 | |
var bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; | |
ms.layer = function(load, unload) { | |
var layer = {}, | |
cache = layer.cache = ms.cache(load, unload).size(512), | |
tile = true, | |
visible = true, | |
zoom, | |
id, | |
map, | |
container = ms.svg("g"), | |
transform, | |
levelZoom, | |
levels = {}; | |
container.setAttribute("class", "layer"); | |
for (var i = -4; i <= -1; i++) levels[i] = container.appendChild(ms.svg("g")); | |
for (var i = 2; i >= 1; i--) levels[i] = container.appendChild(ms.svg("g")); | |
levels[0] = container.appendChild(ms.svg("g")); | |
function zoomIn(z) { | |
var end = levels[0].nextSibling; | |
for (; levelZoom < z; levelZoom++) { | |
// -4, -3, -2, -1, +2, +1, =0 // current order | |
// -3, -2, -1, +2, +1, =0, -4 // insertBefore(-4, end) | |
// -3, -2, -1, +1, =0, -4, +2 // insertBefore(+2, end) | |
// -3, -2, -1, =0, -4, +2, +1 // insertBefore(+1, end) | |
// -4, -3, -2, -1, +2, +1, =0 // relabel | |
container.insertBefore(levels[-4], end); | |
container.insertBefore(levels[2], end); | |
container.insertBefore(levels[1], end); | |
var t = levels[-4]; | |
for (var dz = -4; dz < 2;) levels[dz] = levels[++dz]; | |
levels[dz] = t; | |
} | |
} | |
function zoomOut(z) { | |
var end = levels[0].nextSibling; | |
for (; levelZoom > z; levelZoom--) { | |
// -4, -3, -2, -1, +2, +1, =0 // current order | |
// -4, -3, -2, +2, +1, =0, -1 // insertBefore(-1, end) | |
// +2, -4, -3, -2, +1, =0, -1 // insertBefore(+2, -4) | |
// -4, -3, -2, -1, +2, +1, =0 // relabel | |
container.insertBefore(levels[-1], end); | |
container.insertBefore(levels[2], levels[-4]); | |
var t = levels[2]; | |
for (var dz = 2; dz > -4;) levels[dz] = levels[--dz]; | |
levels[dz] = t; | |
} | |
} | |
function move() { | |
var map = layer.map(), // in case the layer is removed | |
mapZoom = map.zoom(), | |
mapZoomFraction = mapZoom - (mapZoom = Math.round(mapZoom)), | |
mapSize = map.size(), | |
mapAngle = map.angle(), | |
tileSize = map.tileSize(), | |
tileCenter = map.locationCoordinate(map.center()); | |
// set the layer zoom levels | |
if (levelZoom != mapZoom) { | |
if (levelZoom < mapZoom) zoomIn(mapZoom); | |
else if (levelZoom > mapZoom) zoomOut(mapZoom); | |
else levelZoom = mapZoom; | |
for (var z = -4; z <= 2; z++) { | |
var l = levels[z]; | |
l.setAttribute("class", "zoom" + (z < 0 ? "" : "+") + z + " zoom" + (mapZoom + z)); | |
l.setAttribute("transform", "scale(" + Math.pow(2, -z) + ")"); | |
} | |
} | |
// get the coordinates of the four corners | |
var c0 = map.pointCoordinate(tileCenter, zero), | |
c1 = map.pointCoordinate(tileCenter, {x: mapSize.x, y: 0}), | |
c2 = map.pointCoordinate(tileCenter, mapSize), | |
c3 = map.pointCoordinate(tileCenter, {x: 0, y: mapSize.y}); | |
var col = tileCenter.column, row = tileCenter.row; | |
tileCenter.column = Math.round((Math.round(tileSize.x * tileCenter.column) + (mapSize.x & 1) / 2) / tileSize.x); | |
tileCenter.row = Math.round((Math.round(tileSize.y * tileCenter.row) + (mapSize.y & 1) / 2) / tileSize.y); | |
col -= tileCenter.column; | |
row -= tileCenter.row; | |
// set the layer transform | |
var roundedZoomFraction = roundZoom(Math.pow(2, mapZoomFraction)); | |
container.setAttribute("transform", | |
"translate(" + | |
Math.round(mapSize.x / 2 - col * tileSize.x * roundedZoomFraction) + | |
"," + | |
Math.round(mapSize.y / 2 - row * tileSize.y * roundedZoomFraction) + | |
")" + | |
(mapAngle ? "rotate(" + mapAngle / Math.PI * 180 + ")" : "") + | |
(mapZoomFraction ? "scale(" + roundedZoomFraction + ")" : "") + | |
(transform ? transform.zoomFraction(mapZoomFraction) : "")); | |
// layer-specific coordinate transform | |
if (transform) { | |
c0 = transform.unapply(c0); | |
c1 = transform.unapply(c1); | |
c2 = transform.unapply(c2); | |
c3 = transform.unapply(c3); | |
tileCenter = transform.unapply(tileCenter); | |
} | |
// layer-specific zoom transform | |
var tileLevel = zoom ? zoom(c0.zoom) - c0.zoom : 0; | |
if (tileLevel) { | |
var k = Math.pow(2, tileLevel); | |
c0.column *= k; c0.row *= k; | |
c1.column *= k; c1.row *= k; | |
c2.column *= k; c2.row *= k; | |
c3.column *= k; c3.row *= k; | |
c0.zoom = c1.zoom = c2.zoom = c3.zoom += tileLevel; | |
} | |
// tile-specific projection | |
function projection(c) { | |
var zoom = c.zoom, | |
max = zoom < 0 ? 1 : 1 << zoom, | |
column = c.column % max, | |
row = c.row; | |
if (column < 0) column += max; | |
return { | |
locationPoint: function(l) { | |
var c = ms.map.locationCoordinate(l), | |
k = Math.pow(2, zoom - c.zoom); | |
return { | |
x: tileSize.x * (k * c.column - column), | |
y: tileSize.y * (k * c.row - row) | |
}; | |
} | |
}; | |
} | |
// record which tiles are visible | |
var oldLocks = cache.locks(), newLocks = {}; | |
// reset the proxy counts | |
for (var key in oldLocks) { | |
oldLocks[key].proxyCount = 0; | |
} | |
// load the tiles! | |
if (visible && tileLevel > -5 && tileLevel < 3) { | |
var ymax = c0.zoom < 0 ? 1 : 1 << c0.zoom; | |
if (tile) { | |
scanTriangle(c0, c1, c2, 0, ymax, scanLine); | |
scanTriangle(c2, c3, c0, 0, ymax, scanLine); | |
} else { | |
var x = Math.floor((c0.column + c2.column) / 2), | |
y = Math.max(0, Math.min(ymax - 1, Math.floor((c1.row + c3.row) / 2))), | |
z = Math.min(4, c0.zoom); | |
x = x >> z << z; | |
y = y >> z << z; | |
scanLine(x, x + 1, y); | |
} | |
} | |
// scan-line conversion | |
function scanLine(x0, x1, y) { | |
var z = c0.zoom, | |
z0 = 2 - tileLevel, | |
z1 = 4 + tileLevel; | |
for (var x = x0; x < x1; x++) { | |
var t = cache.load({column: x, row: y, zoom: z}, projection); | |
if (!t.ready && !(t.key in newLocks)) { | |
t.proxyRefs = {}; | |
var c, full, proxy; | |
// downsample high-resolution tiles | |
for (var dz = 1; dz <= z0; dz++) { | |
full = true; | |
for (var dy = 0, k = 1 << dz; dy <= k; dy++) { | |
for (var dx = 0; dx <= k; dx++) { | |
proxy = cache.peek(c = { | |
column: (x << dz) + dx, | |
row: (y << dz) + dy, | |
zoom: z + dz | |
}); | |
if (proxy && proxy.ready) { | |
newLocks[proxy.key] = cache.load(c); | |
proxy.proxyCount++; | |
t.proxyRefs[proxy.key] = proxy; | |
} else { | |
full = false; | |
} | |
} | |
} | |
if (full) break; | |
} | |
// upsample low-resolution tiles | |
if (!full) { | |
for (var dz = 1; dz <= z1; dz++) { | |
proxy = cache.peek(c = { | |
column: x >> dz, | |
row: y >> dz, | |
zoom: z - dz | |
}); | |
if (proxy && proxy.ready) { | |
newLocks[proxy.key] = cache.load(c); | |
proxy.proxyCount++; | |
t.proxyRefs[proxy.key] = proxy; | |
break; | |
} | |
} | |
} | |
} | |
newLocks[t.key] = t; | |
} | |
} | |
function roundZoom(z) { | |
return Math.round(z * 256) / 256; | |
} | |
// position tiles | |
for (var key in newLocks) { | |
var t = newLocks[key], | |
k = roundZoom(Math.pow(2, t.level = t.zoom - tileCenter.zoom)); | |
var transform = "translate(" + | |
Math.round(t.x = tileSize.x * (t.column - tileCenter.column * k)) + "px" + "," + | |
Math.round(t.y = tileSize.y * (t.row - tileCenter.row * k)) + "px" + ")"; | |
d3.select(t.element).style("transform", transform); | |
d3.select(t.element).style("-webkit-transform", transform); | |
d3.select(t.element).style("-ms-transform", transform); | |
} | |
// remove tiles that are no longer visible | |
for (var key in oldLocks) { | |
if (!(key in newLocks)) { | |
var t = cache.unload(key); | |
t.element.parentNode.removeChild(t.element); | |
delete t.proxyRefs; | |
} | |
} | |
// append tiles that are now visible | |
for (var key in newLocks) { | |
var t = newLocks[key]; | |
if (t.element.parentNode != levels[t.level]) { | |
levels[t.level].appendChild(t.element); | |
if (layer.show) layer.show(t); | |
} | |
} | |
// flush the cache, clearing no-longer-needed tiles | |
cache.flush(); | |
// dispatch the move event | |
layer.dispatch({type: "move"}); | |
} | |
// remove proxy tiles when tiles load | |
function cleanup(e) { | |
if (e.tile.proxyRefs) { | |
for (var proxyKey in e.tile.proxyRefs) { | |
var proxyTile = e.tile.proxyRefs[proxyKey]; | |
if ((--proxyTile.proxyCount <= 0) && cache.unload(proxyKey)) { | |
proxyTile.element.parentNode.removeChild(proxyTile.element); | |
} | |
} | |
delete e.tile.proxyRefs; | |
} | |
} | |
layer.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
if (map == x) { | |
container.parentNode.appendChild(container); // move to end | |
return layer; | |
} | |
map.off("move", move).off("resize", move); | |
container.parentNode.removeChild(container); | |
} | |
map = x; | |
if (map) { | |
map.svgContainer().appendChild(container); | |
if (layer.init) layer.init(container); | |
map.on("move", move).on("resize", move); | |
move(); | |
} | |
return layer; | |
}; | |
layer.container = function() { | |
return container; | |
}; | |
layer.levels = function() { | |
return levels; | |
}; | |
layer.id = function(x) { | |
if (!arguments.length) return id; | |
id = x; | |
container.setAttribute("id", x); | |
return layer; | |
}; | |
layer.visible = function(x) { | |
if (!arguments.length) return visible; | |
if (visible = x) container.removeAttribute("visibility"); | |
else container.setAttribute("visibility", "hidden"); | |
if (map) move(); | |
return layer; | |
}; | |
layer.transform = function(x) { | |
if (!arguments.length) return transform; | |
transform = x; | |
if (map) move(); | |
return layer; | |
}; | |
layer.zoom = function(x) { | |
if (!arguments.length) return zoom; | |
zoom = typeof x == "function" || x == null ? x : function() { return x; }; | |
if (map) move(); | |
return layer; | |
}; | |
layer.tile = function(x) { | |
if (!arguments.length) return tile; | |
tile = x; | |
if (map) move(); | |
return layer; | |
}; | |
layer.reload = function() { | |
cache.clear(); | |
if (map) move(); | |
return layer; | |
}; | |
layer.dispatch = ms.dispatch(layer); | |
layer.on("load", cleanup); | |
return layer; | |
}; | |
// scan-line conversion | |
function edge(a, b) { | |
if (a.row > b.row) { var t = a; a = b; b = t; } | |
return { | |
x0: a.column, | |
y0: a.row, | |
x1: b.column, | |
y1: b.row, | |
dx: b.column - a.column, | |
dy: b.row - a.row | |
}; | |
} | |
// scan-line conversion | |
function scanSpans(e0, e1, ymin, ymax, scanLine) { | |
var y0 = Math.max(ymin, Math.floor(e1.y0)), | |
y1 = Math.min(ymax, Math.ceil(e1.y1)); | |
// sort edges by x-coordinate | |
if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ? | |
(e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) : | |
(e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) { | |
var t = e0; e0 = e1; e1 = t; | |
} | |
// scan lines! | |
var m0 = e0.dx / e0.dy, | |
m1 = e1.dx / e1.dy, | |
d0 = e0.dx > 0, // use y + 1 to compute x0 | |
d1 = e1.dx < 0; // use y + 1 to compute x1 | |
for (var y = y0; y < y1; y++) { | |
var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0, | |
x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0; | |
scanLine(Math.floor(x1), Math.ceil(x0), y); | |
} | |
} | |
// scan-line conversion | |
function scanTriangle(a, b, c, ymin, ymax, scanLine) { | |
var ab = edge(a, b), | |
bc = edge(b, c), | |
ca = edge(c, a); | |
// sort edges by y-length | |
if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; } | |
if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; } | |
if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; } | |
// scan span! scan span! | |
if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine); | |
if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine); | |
} | |
ms.image = function() { | |
var image = ms.layer(load, unload), | |
url; | |
function load(tile) { | |
var element = tile.element = ms.svg("image"), size = image.map().tileSize(); | |
element.setAttribute("preserveAspectRatio", "none"); | |
element.setAttribute("width", size.x); | |
element.setAttribute("height", size.y); | |
if (typeof url == "function") { | |
element.setAttribute("opacity", 0); | |
var tileUrl = url(tile); | |
if (tileUrl != null) { | |
tile.request = ms.queue.image(element, tileUrl, function(img) { | |
delete tile.request; | |
tile.ready = true; | |
tile.img = img; | |
element.removeAttribute("opacity"); | |
image.dispatch({type: "load", tile: tile}); | |
}); | |
} else { | |
tile.ready = true; | |
image.dispatch({type: "load", tile: tile}); | |
} | |
} else { | |
tile.ready = true; | |
if (url != null) element.setAttributeNS(ms.ns.xlink, "href", url); | |
image.dispatch({type: "load", tile: tile}); | |
} | |
} | |
function unload(tile) { | |
if (tile.request) tile.request.abort(true); | |
} | |
image.url = function(x) { | |
if (!arguments.length) return url; | |
url = typeof x == "string" && /{.}/.test(x) ? ms.url(x) : x; | |
return image.reload(); | |
}; | |
return image; | |
}; | |
ms.geoJson = function(fetch) { | |
var geoJson = ms.layer(load, unload), | |
container = geoJson.container(), | |
url, | |
clip = false, | |
clipId = "org.polymaps." + ms.id(), | |
clipHref = "url(#" + clipId + ")", | |
clipPath = container.insertBefore(ms.svg("clipPath"), container.firstChild), | |
clipRect = clipPath.appendChild(ms.svg("rect")), | |
scale = "fixed", | |
zoom = null, | |
pointRadius = 4.5, | |
features, | |
tileBackground = false, | |
transformData, | |
key = (function() { | |
var k = 0; | |
return function(f) { | |
k = k + 1 | 0; | |
return k; | |
}; | |
})(), | |
selection, | |
dataGeneration = 0, | |
selectionGeneration = 0; | |
container.setAttribute("fill-rule", "evenodd"); | |
clipPath.setAttribute("id", clipId); | |
if (!arguments.length) fetch = ms.queue.json; | |
function projection(proj) { | |
var l = {lat: 0, lon: 0}; | |
return function(coordinates, c) { | |
l.lat = coordinates[1]; | |
l.lon = coordinates[0]; | |
var p = proj(l); | |
c.x = p.x; | |
c.y = p.y; | |
return p; | |
}; | |
} | |
function rescale(o, e, k) { | |
var g = o.geometry; | |
return g.type in rescales && rescales[g.type](o, e, k); | |
} | |
var rescales = { | |
Point: function (o, e, k) { | |
e.setAttribute("transform", "translate(" + o.x + "," + o.y + ")" + k); | |
}, | |
MultiPoint: function (o, e, k) { | |
e.setAttribute("transform", "translate(" + o.x + "," + o.y + ")" + k); | |
} | |
}; | |
// Create path projecting WGS84 spherical Mercator coordinates. | |
function projectSpherical(tileProj) { | |
return d3.geo.path().projection({ | |
stream: function(stream) { | |
return { | |
point: function(x, y) { | |
// Latitudes at the poles (or beyond!) result in unrenderable NaN's and Infinities. | |
var epsilon = 1.0e-6; | |
y = Math.min(90 - epsilon, y); | |
y = Math.max(-90 + epsilon, y); | |
var p = tileProj.locationPoint({ lon: x, lat: y }); | |
stream.point(Math.round(2 * p.x) / 2, Math.round(2 * p.y) / 2); | |
}, | |
sphere: function() { stream.sphere(); }, | |
lineStart: function() { stream.lineStart(); }, | |
lineEnd: function() { stream.lineEnd(); }, | |
polygonStart: function() { stream.polygonStart(); }, | |
polygonEnd: function() { stream.polygonEnd(); } | |
}; | |
} | |
}); | |
} | |
function load(tile, proj) { | |
var g = tile.element = ms.svg("g"); | |
tile.proj = proj(tile); | |
tile.fetched = []; | |
tile.features = []; | |
tile.draw = function() { | |
draw(g, tile); | |
}; | |
function update(data) { | |
if (geoJson.tile() && tileBackground) { | |
var tileSize = geoJson.map().tileSize(); | |
d3.select(g.insertBefore(ms.svg("rect"), g.firstChild)) | |
.attr("width", tileSize.x) | |
.attr("height", tileSize.x) | |
.attr("class", "tile-background"); | |
} | |
tile.fetched = data.features; | |
tile.draw(); | |
tile.ready = true; | |
geoJson.dispatch({type: "load", tile: tile, features: tile.features}); | |
} | |
if (url != null) { | |
tile.request = fetch(typeof url == "function" ? url(tile) : url, update); | |
} else { | |
update({type: "FeatureCollection", features: features || []}); | |
} | |
} | |
function copyObject(source) { | |
var copy = {}; | |
for (var property in source) { | |
copy[property] = source[property]; | |
} | |
return copy; | |
} | |
function projectPoint(p, proj) { | |
proj(p.geometry.coordinates, p); | |
return p; | |
} | |
function projectPointsForMultiPoint(mp, proj) { | |
var length = mp.geometry.coordinates.length; | |
var points = []; | |
for (var i = 0; i < length; i++) { | |
var p = copyObject(mp); | |
proj(p.geometry.coordinates[i], p); | |
points.push(p); | |
} | |
return points; | |
} | |
function draw(g, tile) { | |
var proj = projection(tile.proj.locationPoint), | |
path = projectSpherical(tile.proj), | |
pathFeatures = [], | |
pointFeatures = [], | |
features = transformData ? transformData(tile.fetched, tile) : tile.fetched, | |
updated = []; | |
features.forEach(function(f) { | |
if (f.geometry.type === "Point") | |
pointFeatures.push(projectPoint(f, proj)); | |
else if (f.geometry.type === "MultiPoint") | |
pointFeatures.push.apply(pointFeatures, projectPointsForMultiPoint(f, proj)); | |
else | |
pathFeatures.push(f); | |
}); | |
var pathUpdate = d3.select(g) | |
.selectAll("path") | |
.data(pathFeatures, key); | |
pathUpdate.enter() | |
.append("path") | |
.attr("d", function(f) { return path(f); }); | |
pathUpdate.exit().remove(); | |
pathUpdate.each(function(f) { updated.push({ element: this, data: f }); }); | |
var initialScale = ""; | |
if (scale == "fixed") { | |
initialScale = "scale(" + Math.pow(2, tile.zoom - (tile.scale = geoJson.map().zoom())) + ")"; | |
} | |
var pointUpdate = d3.select(g) | |
.selectAll("circle") | |
.data(pointFeatures, key); | |
pointUpdate.enter() | |
.append("circle") | |
.attr("transform", function(f) { | |
return "translate(" + f.x + "," + f.y + ")" + initialScale; | |
}) | |
.attr("r", pointRadius); | |
pointUpdate.exit().remove(); | |
pointUpdate.each(function(f) { updated.push({ element: this, data: f }); }); | |
if (selection) { | |
pathUpdate.push.apply(pathUpdate, pointUpdate); | |
selection(pathUpdate); | |
} | |
tile.features = updated; | |
tile.dataGeneration = dataGeneration; | |
tile.selectionGeneration = selectionGeneration; | |
} | |
function unload(tile) { | |
if (tile.request) tile.request.abort(true); | |
} | |
function move() { | |
var zoom = geoJson.map().zoom(), | |
tiles = geoJson.cache.locks(), // visible tiles | |
key, // key in locks | |
tile, // locks[key] | |
features, // tile.features | |
i, // current feature index | |
n, // current feature count, features.length | |
feature, // features[i] | |
k; // scale transform | |
if (scale == "fixed") { | |
for (key in tiles) { | |
if ((tile = tiles[key]).scale != zoom) { | |
k = "scale(" + Math.pow(2, tile.zoom - zoom) + ")"; | |
i = -1; | |
n = (features = tile.features).length; | |
while (++i < n) rescale((feature = features[i]).data, feature.element, k); | |
tile.scale = zoom; | |
} | |
} | |
} | |
} | |
geoJson.tileBackground = function(x) { | |
if (!arguments.length) return tileBackground; | |
tileBackground = x; | |
return geoJson; | |
}; | |
geoJson.selection = function(x) { | |
if (!arguments.length) return selection; | |
selection = x; | |
selectionGeneration = selectionGeneration + 1 | 0; | |
return geoJson.reshow(); | |
}; | |
geoJson.transformData = function(x) { | |
if (!arguments.length) return transformData; | |
transformData = x; | |
dataGeneration = dataGeneration + 1 | 0; | |
return geoJson.reshow(); | |
}; | |
geoJson.url = function(x) { | |
if (!arguments.length) return url; | |
url = typeof x == "string" && /{.}/.test(x) ? ms.url(x) : x; | |
if (url != null) features = null; | |
if (typeof url == "string") geoJson.tile(false); | |
return geoJson.reload(); | |
}; | |
geoJson.features = function(x) { | |
if (!arguments.length) return features; | |
if (features = x) { | |
url = null; | |
geoJson.tile(false); | |
} | |
return geoJson.reload(); | |
}; | |
geoJson.clip = function(x) { | |
if (!arguments.length) return clip; | |
if (clip) container.removeChild(clipPath); | |
if (clip = x) container.insertBefore(clipPath, container.firstChild); | |
var locks = geoJson.cache.locks(); | |
for (var key in locks) { | |
if (clip) locks[key].element.setAttribute("clip-path", clipHref); | |
else locks[key].element.removeAttribute("clip-path"); | |
} | |
return geoJson; | |
}; | |
var __tile__ = geoJson.tile; | |
geoJson.tile = function(x) { | |
if (arguments.length && !x) geoJson.clip(x); | |
return __tile__.apply(geoJson, arguments); | |
}; | |
var __map__ = geoJson.map; | |
geoJson.map = function(x) { | |
if (x && clipRect) { | |
var size = x.tileSize(); | |
clipRect.setAttribute("width", size.x); | |
clipRect.setAttribute("height", size.y); | |
} | |
return __map__.apply(geoJson, arguments); | |
}; | |
geoJson.scale = function(x) { | |
if (!arguments.length) return scale; | |
if (scale = x) geoJson.on("move", move); | |
else geoJson.off("move", move); | |
if (geoJson.map()) move(); | |
return geoJson; | |
}; | |
geoJson.show = function(tile) { | |
if (clip) tile.element.setAttribute("clip-path", clipHref); | |
else tile.element.removeAttribute("clip-path"); | |
if (transformData && tile.dataGeneration !== dataGeneration) { | |
tile.draw(); | |
} | |
else if (selection && tile.selectionGeneration !== selectionGeneration) { | |
var s = d3.select(tile.element).selectAll("path,circle"); | |
selection(s); | |
tile.selectionGeneration = selectionGeneration; | |
} | |
geoJson.dispatch({type: "show", tile: tile, features: tile.features}); | |
return geoJson; | |
}; | |
geoJson.reshow = function() { | |
var locks = geoJson.cache.locks(); | |
for (var key in locks) geoJson.show(locks[key]); | |
return geoJson; | |
}; | |
return geoJson; | |
}; | |
ms.topoJson = function(fetch) { | |
if (!arguments.length) fetch = ms.queue.json; | |
var classify, | |
staticTopology; | |
function groupFeatures(features) { | |
if (!classify) | |
return features; | |
var classIndices = {}; | |
var groupedFeatures = []; | |
features.forEach(function(f) { | |
var c = classify(f); | |
var index = classIndices[c]; | |
if (index === undefined) { | |
index = groupedFeatures.push([]) - 1; | |
classIndices[c] = index; | |
} | |
groupedFeatures[index].push(f); | |
}); | |
return groupedFeatures.map(function(g) { | |
return { | |
type: 'GeometryCollection', | |
geometries: g | |
}; | |
}); | |
} | |
var topologyFeatures = function(topology) { | |
function convert(topology, object, layer, features) { | |
var featureOrCollection = topojson.feature(topology, object), | |
layerFeatures; | |
if (featureOrCollection.type === "FeatureCollection") { | |
layerFeatures = featureOrCollection.features; | |
} else { | |
layerFeatures = [featureOrCollection]; | |
} | |
layerFeatures.forEach(function(f) { | |
f.properties.layer = layer; | |
}); | |
features.push.apply(features, layerFeatures); | |
} | |
var features = []; | |
for (var o in topology.objects) { | |
convert(topology, topology.objects[o], o, features); | |
} | |
features = groupFeatures(features); | |
return features; | |
}; | |
var topoToGeo = function(url, callback) { | |
return fetch(url, function(topology) { | |
callback({ | |
type: "FeatureCollection", | |
features: topologyFeatures(topology) | |
}); | |
}); | |
}; | |
var topoJson = ms.geoJson(topoToGeo); | |
topoJson.topologyFeatures = function(x) { | |
if (!arguments.length) return topologyFeatures; | |
topologyFeatures = x; | |
return topoJson; | |
}; | |
topoJson.classify = function(x) { | |
if (!arguments.length) return classify; | |
classify = x; | |
return topoJson; | |
}; | |
topoJson.staticTopology = function(x) { | |
if (!arguments.length) return staticTopology; | |
staticTopology = x; | |
return topoJson.features(staticTopology ? topologyFeatures(staticTopology) : null); | |
}; | |
return topoJson; | |
}; | |
ms.dblclick = function() { | |
var dblclick = {}, | |
zoom = "mouse", | |
map, | |
container; | |
function handle(e) { | |
var z = map.zoom(); | |
if (e.shiftKey) z = Math.ceil(z) - z - 1; | |
else z = 1 - z + Math.floor(z); | |
if (zoom === "mouse") | |
map.zoomBy(z, map.mouse(e)); | |
else | |
map.zoomBy(z); | |
} | |
dblclick.zoom = function(x) { | |
if (!arguments.length) return zoom; | |
zoom = x; | |
return dblclick; | |
}; | |
dblclick.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("dblclick", handle, false); | |
container = null; | |
} | |
if (map = x) { | |
container = map.container(); | |
container.addEventListener("dblclick", handle, false); | |
} | |
return dblclick; | |
}; | |
return dblclick; | |
}; | |
ms.drag = function() { | |
var drag = {}, | |
map, | |
container, | |
dragging; | |
function mousedown(e) { | |
if (e.shiftKey) return; | |
dragging = { | |
x: e.clientX, | |
y: e.clientY | |
}; | |
map.focusableParent().focus(); | |
e.preventDefault(); | |
document.body.style.setProperty("cursor", "move", null); | |
} | |
function mousemove(e) { | |
if (!dragging) return; | |
map.panBy({x: e.clientX - dragging.x, y: e.clientY - dragging.y}); | |
dragging.x = e.clientX; | |
dragging.y = e.clientY; | |
} | |
function mouseup(e) { | |
if (!dragging) return; | |
mousemove(e); | |
dragging = null; | |
document.body.style.removeProperty("cursor"); | |
} | |
drag.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("mousedown", mousedown, false); | |
container = null; | |
} | |
if (map = x) { | |
container = map.container(); | |
container.addEventListener("mousedown", mousedown, false); | |
} | |
return drag; | |
}; | |
window.addEventListener("mousemove", mousemove, false); | |
window.addEventListener("mouseup", mouseup, false); | |
return drag; | |
}; | |
ms.wheel = function() { | |
var wheel = {}, | |
timePrev = 0, | |
last = 0, | |
smooth = true, | |
zoom = "mouse", | |
location, | |
map, | |
container; | |
function move(e) { | |
location = null; | |
} | |
// mousewheel events are totally broken! | |
// https://bugs.webkit.org/show_bug.cgi?id=40441 | |
// not only that, but Chrome and Safari differ in re. to acceleration! | |
var inner = document.createElement("div"), | |
outer = document.createElement("div"); | |
outer.style.visibility = "hidden"; | |
outer.style.top = "0px"; | |
outer.style.height = "0px"; | |
outer.style.width = "0px"; | |
outer.style.overflowY = "scroll"; | |
inner.style.height = "2000px"; | |
outer.appendChild(inner); | |
document.body.appendChild(outer); | |
function mousewheel(e) { | |
var delta = e.wheelDelta || -e.detail, | |
point; | |
/* Detect the pixels that would be scrolled by this wheel event. */ | |
if (delta) { | |
if (smooth) { | |
try { | |
outer.scrollTop = 1000; | |
outer.dispatchEvent(e); | |
delta = 1000 - outer.scrollTop; | |
} catch (error) { | |
// Derp! Hope for the best? | |
} | |
delta *= 0.001; | |
} | |
/* If smooth zooming is disabled, batch events into unit steps. */ | |
else { | |
var timeNow = Date.now(); | |
if (timeNow - timePrev > 200) { | |
delta = delta > 0 ? +1 : -1; | |
timePrev = timeNow; | |
} else { | |
delta = 0; | |
} | |
} | |
} | |
if (delta) { | |
switch (zoom) { | |
case "mouse": { | |
point = map.mouse(e); | |
if (!location) location = map.pointLocation(point); | |
map.off("move", move).zoomBy(delta, point, location).on("move", move); | |
break; | |
} | |
case "location": { | |
map.zoomBy(delta, map.locationPoint(location), location); | |
break; | |
} | |
default: { // center | |
map.zoomBy(delta); | |
break; | |
} | |
} | |
} | |
e.preventDefault(); | |
return false; // for Firefox | |
} | |
wheel.smooth = function(x) { | |
if (!arguments.length) return smooth; | |
smooth = x; | |
return wheel; | |
}; | |
wheel.zoom = function(x, l) { | |
if (!arguments.length) return zoom; | |
zoom = x; | |
location = l; | |
if (map) { | |
if (zoom == "mouse") map.on("move", move); | |
else map.off("move", move); | |
} | |
return wheel; | |
}; | |
wheel.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("mousemove", move, false); | |
container.removeEventListener("mousewheel", mousewheel, false); | |
container.removeEventListener("MozMousePixelScroll", mousewheel, false); | |
container = null; | |
map.off("move", move); | |
} | |
if (map = x) { | |
if (zoom == "mouse") map.on("move", move); | |
container = map.container(); | |
container.addEventListener("mousemove", move, false); | |
container.addEventListener("mousewheel", mousewheel, false); | |
container.addEventListener("MozMousePixelScroll", mousewheel, false); | |
} | |
return wheel; | |
}; | |
return wheel; | |
}; | |
ms.arrow = function() { | |
var arrow = {}, | |
key = {left: 0, right: 0, up: 0, down: 0}, | |
last = 0, | |
repeatTimer, | |
repeatDelay = 250, | |
repeatInterval = 50, | |
speed = 16, | |
map, | |
parent; | |
function keydown(e) { | |
if (e.ctrlKey || e.altKey || e.metaKey) return; | |
var now = Date.now(), dx = 0, dy = 0; | |
switch (e.keyCode) { | |
case 37: { | |
if (!key.left) { | |
last = now; | |
key.left = 1; | |
if (!key.right) dx = speed; | |
} | |
break; | |
} | |
case 39: { | |
if (!key.right) { | |
last = now; | |
key.right = 1; | |
if (!key.left) dx = -speed; | |
} | |
break; | |
} | |
case 38: { | |
if (!key.up) { | |
last = now; | |
key.up = 1; | |
if (!key.down) dy = speed; | |
} | |
break; | |
} | |
case 40: { | |
if (!key.down) { | |
last = now; | |
key.down = 1; | |
if (!key.up) dy = -speed; | |
} | |
break; | |
} | |
default: return; | |
} | |
if (dx || dy) map.panBy({x: dx, y: dy}); | |
if (!repeatTimer && (key.left | key.right | key.up | key.down)) { | |
repeatTimer = setInterval(repeat, repeatInterval); | |
} | |
e.preventDefault(); | |
} | |
function keyup(e) { | |
last = Date.now(); | |
switch (e.keyCode) { | |
case 37: key.left = 0; break; | |
case 39: key.right = 0; break; | |
case 38: key.up = 0; break; | |
case 40: key.down = 0; break; | |
default: return; | |
} | |
if (repeatTimer && !(key.left | key.right | key.up | key.down)) { | |
repeatTimer = clearInterval(repeatTimer); | |
} | |
e.preventDefault(); | |
} | |
function keypress(e) { | |
switch (e.charCode) { | |
case 45: case 95: map.zoom(Math.ceil(map.zoom()) - 1); break; // - _ | |
case 43: case 61: map.zoom(Math.floor(map.zoom()) + 1); break; // = + | |
default: return; | |
} | |
e.preventDefault(); | |
} | |
function repeat() { | |
if (!map) return; | |
if (Date.now() < last + repeatDelay) return; | |
var dx = (key.left - key.right) * speed, | |
dy = (key.up - key.down) * speed; | |
if (dx || dy) map.panBy({x: dx, y: dy}); | |
} | |
arrow.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
parent.removeEventListener("keypress", keypress, false); | |
parent.removeEventListener("keydown", keydown, false); | |
parent.removeEventListener("keyup", keyup, false); | |
parent = null; | |
} | |
if (map = x) { | |
parent = map.focusableParent(); | |
parent.addEventListener("keypress", keypress, false); | |
parent.addEventListener("keydown", keydown, false); | |
parent.addEventListener("keyup", keyup, false); | |
} | |
return arrow; | |
}; | |
arrow.speed = function(x) { | |
if (!arguments.length) return speed; | |
speed = x; | |
return arrow; | |
}; | |
return arrow; | |
}; | |
ms.hash = function() { | |
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); | |
}; | |
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); | |
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; | |
}; | |
ms.touch = function() { | |
var touch = {}, | |
map, | |
container, | |
rotate = false, | |
last = 0, | |
zoom, | |
angle, | |
locations = {}; // touch identifier -> location | |
window.addEventListener("touchmove", touchmove, false); | |
function touchstart(e) { | |
var i = -1, | |
n = e.touches.length, | |
t = Date.now(); | |
// doubletap detection | |
if ((n == 1) && (t - last < 300)) { | |
var z = map.zoom(); | |
map.zoomBy(1 - z + Math.floor(z), map.mouse(e.touches[0])); | |
e.preventDefault(); | |
} | |
last = t; | |
// store original zoom & touch locations | |
zoom = map.zoom(); | |
angle = map.angle(); | |
while (++i < n) { | |
t = e.touches[i]; | |
locations[t.identifier] = map.pointLocation(map.mouse(t)); | |
} | |
} | |
function touchmove(e) { | |
switch (e.touches.length) { | |
case 1: { // single-touch pan | |
var t0 = e.touches[0]; | |
map.zoomBy(0, map.mouse(t0), locations[t0.identifier]); | |
e.preventDefault(); | |
break; | |
} | |
case 2: { // double-touch pan + zoom + rotate | |
var t0 = e.touches[0], | |
t1 = e.touches[1], | |
p0 = map.mouse(t0), | |
p1 = map.mouse(t1), | |
p2 = {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2}, // center point | |
c0 = ms.map.locationCoordinate(locations[t0.identifier]), | |
c1 = ms.map.locationCoordinate(locations[t1.identifier]), | |
c2 = {row: (c0.row + c1.row) / 2, column: (c0.column + c1.column) / 2, zoom: 0}, | |
l2 = ms.map.coordinateLocation(c2); // center location | |
map.zoomBy(Math.log(e.scale) / Math.LN2 + zoom - map.zoom(), p2, l2); | |
if (rotate) map.angle(e.rotation / 180 * Math.PI + angle); | |
e.preventDefault(); | |
break; | |
} | |
} | |
} | |
touch.rotate = function(x) { | |
if (!arguments.length) return rotate; | |
rotate = x; | |
return touch; | |
}; | |
touch.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("touchstart", touchstart, false); | |
container = null; | |
} | |
if (map = x) { | |
container = map.container(); | |
container.addEventListener("touchstart", touchstart, false); | |
} | |
return touch; | |
}; | |
return touch; | |
}; | |
// Default map controls. | |
ms.interact = function() { | |
var interact = {}, | |
drag = ms.drag(), | |
wheel = ms.wheel(), | |
dblclick = ms.dblclick(), | |
touch = ms.touch(), | |
arrow = ms.arrow(); | |
interact.map = function(x) { | |
drag.map(x); | |
wheel.map(x); | |
dblclick.map(x); | |
touch.map(x); | |
arrow.map(x); | |
return interact; | |
}; | |
return interact; | |
}; | |
ms.compass = function() { | |
var compass = {}, | |
g = ms.svg("g"), | |
ticks = {}, | |
r = 30, | |
speed = 16, | |
last = 0, | |
repeatDelay = 250, | |
repeatInterval = 50, | |
position = "top-left", // top-left, top-right, bottom-left, bottom-right | |
zoomStyle = "small", // none, small, big | |
zoomContainer, | |
panStyle = "small", // none, small | |
panTimer, | |
panDirection, | |
panContainer, | |
drag, | |
dragRect = ms.svg("rect"), | |
map, | |
container, | |
window; | |
g.setAttribute("class", "compass"); | |
dragRect.setAttribute("class", "back fore"); | |
dragRect.setAttribute("pointer-events", "none"); | |
dragRect.setAttribute("display", "none"); | |
function panStart(e) { | |
g.setAttribute("class", "compass active"); | |
if (!panTimer) panTimer = setInterval(panRepeat, repeatInterval); | |
if (panDirection) map.panBy(panDirection); | |
last = Date.now(); | |
return cancel(e); | |
} | |
function panRepeat() { | |
if (panDirection && (Date.now() > last + repeatDelay)) { | |
map.panBy(panDirection); | |
} | |
} | |
function mousedown(e) { | |
if (e.shiftKey) { | |
drag = {x0: map.mouse(e)}; | |
map.focusableParent().focus(); | |
return cancel(e); | |
} | |
} | |
function mousemove(e) { | |
if (!drag) return; | |
drag.x1 = map.mouse(e); | |
dragRect.setAttribute("x", Math.min(drag.x0.x, drag.x1.x)); | |
dragRect.setAttribute("y", Math.min(drag.x0.y, drag.x1.y)); | |
dragRect.setAttribute("width", Math.abs(drag.x0.x - drag.x1.x)); | |
dragRect.setAttribute("height", Math.abs(drag.x0.y - drag.x1.y)); | |
dragRect.removeAttribute("display"); | |
} | |
function mouseup(e) { | |
g.setAttribute("class", "compass"); | |
if (drag) { | |
if (drag.x1) { | |
map.extent([ | |
map.pointLocation({ | |
x: Math.min(drag.x0.x, drag.x1.x), | |
y: Math.max(drag.x0.y, drag.x1.y) | |
}), | |
map.pointLocation({ | |
x: Math.max(drag.x0.x, drag.x1.x), | |
y: Math.min(drag.x0.y, drag.x1.y) | |
}) | |
]); | |
dragRect.setAttribute("display", "none"); | |
} | |
drag = null; | |
} | |
if (panTimer) { | |
clearInterval(panTimer); | |
panTimer = 0; | |
} | |
} | |
function panBy(x) { | |
return function() { | |
if (x) | |
this.setAttribute("class", "active"); | |
else | |
this.removeAttribute("class"); | |
panDirection = x; | |
}; | |
} | |
function zoomBy(x) { | |
return function(e) { | |
g.setAttribute("class", "compass active"); | |
var z = map.zoom(); | |
map.zoom(x < 0 ? Math.ceil(z) - 1 : Math.floor(z) + 1); | |
return cancel(e); | |
}; | |
} | |
function zoomTo(x) { | |
return function(e) { | |
map.zoom(x); | |
return cancel(e); | |
}; | |
} | |
function zoomOver() { | |
this.setAttribute("class", "active"); | |
} | |
function zoomOut() { | |
this.removeAttribute("class"); | |
} | |
function cancel(e) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
return false; | |
} | |
function pan(by) { | |
var x = Math.SQRT1_2 * r, | |
y = r * 0.7, | |
z = r * 0.2, | |
g = ms.svg("g"), | |
dir = g.appendChild(ms.svg("path")), | |
chv = g.appendChild(ms.svg("path")); | |
dir.setAttribute("class", "direction"); | |
dir.setAttribute("pointer-events", "all"); | |
dir.setAttribute("d", "M0,0L" + x + "," + x + "A" + r + "," + r + " 0 0,1 " + -x + "," + x + "Z"); | |
chv.setAttribute("class", "chevron"); | |
chv.setAttribute("d", "M" + z + "," + (y - z) + "L0," + y + " " + -z + "," + (y - z)); | |
chv.setAttribute("pointer-events", "none"); | |
g.addEventListener("mousedown", panStart, false); | |
g.addEventListener("mouseover", panBy(by), false); | |
g.addEventListener("mouseout", panBy(null), false); | |
g.addEventListener("dblclick", cancel, false); | |
return g; | |
} | |
function zoom(by) { | |
var x = r * 0.4, | |
y = x / 2, | |
g = ms.svg("g"), | |
back = g.appendChild(ms.svg("path")), | |
dire = g.appendChild(ms.svg("path")), | |
chev = g.appendChild(ms.svg("path")), | |
fore = g.appendChild(ms.svg("path")); | |
back.setAttribute("class", "back"); | |
back.setAttribute("d", "M" + -x + ",0V" + -x + "A" + x + "," + x + " 0 1,1 " + x + "," + -x + "V0Z"); | |
dire.setAttribute("class", "direction"); | |
dire.setAttribute("d", back.getAttribute("d")); | |
chev.setAttribute("class", "chevron"); | |
chev.setAttribute("d", "M" + -y + "," + -x + "H" + y + (by > 0 ? "M0," + (-x - y) + "V" + -y : "")); | |
fore.setAttribute("class", "fore"); | |
fore.setAttribute("fill", "none"); | |
fore.setAttribute("d", back.getAttribute("d")); | |
g.addEventListener("mousedown", zoomBy(by), false); | |
g.addEventListener("mouseover", zoomOver, false); | |
g.addEventListener("mouseout", zoomOut, false); | |
g.addEventListener("dblclick", cancel, false); | |
return g; | |
} | |
function tick(i) { | |
var x = r * 0.2, | |
y = r * 0.4, | |
g = ms.svg("g"), | |
back = g.appendChild(ms.svg("rect")), | |
chev = g.appendChild(ms.svg("path")); | |
back.setAttribute("pointer-events", "all"); | |
back.setAttribute("fill", "none"); | |
back.setAttribute("x", -y); | |
back.setAttribute("y", -0.75 * y); | |
back.setAttribute("width", 2 * y); | |
back.setAttribute("height", 1.5 * y); | |
chev.setAttribute("class", "chevron"); | |
chev.setAttribute("d", "M" + -x + ",0H" + x); | |
g.addEventListener("mousedown", zoomTo(i), false); | |
g.addEventListener("dblclick", cancel, false); | |
return g; | |
} | |
function move() { | |
var x = r + 6, y = x, size = map.size(); | |
switch (position) { | |
case "top-left": break; | |
case "top-right": x = size.x - x; break; | |
case "bottom-left": y = size.y - y; break; | |
case "bottom-right": x = size.x - x; y = size.y - y; break; | |
} | |
g.setAttribute("transform", "translate(" + x + "," + y + ")"); | |
dragRect.setAttribute("transform", "translate(" + -x + "," + -y + ")"); | |
for (var i in ticks) { | |
if (i == map.zoom()) | |
ticks[i].setAttribute("class", "active"); | |
else | |
ticks[i].removeAttribute("class"); | |
} | |
} | |
function draw() { | |
while (g.lastChild) g.removeChild(g.lastChild); | |
g.appendChild(dragRect); | |
if (panStyle != "none") { | |
panContainer = g.appendChild(ms.svg("g")); | |
panContainer.setAttribute("class", "pan"); | |
var back = panContainer.appendChild(ms.svg("circle")); | |
back.setAttribute("class", "back"); | |
back.setAttribute("r", r); | |
var s = panContainer.appendChild(pan({x: 0, y: -speed})); | |
s.setAttribute("transform", "rotate(0)"); | |
var w = panContainer.appendChild(pan({x: speed, y: 0})); | |
w.setAttribute("transform", "rotate(90)"); | |
var n = panContainer.appendChild(pan({x: 0, y: speed})); | |
n.setAttribute("transform", "rotate(180)"); | |
var e = panContainer.appendChild(pan({x: -speed, y: 0})); | |
e.setAttribute("transform", "rotate(270)"); | |
var fore = panContainer.appendChild(ms.svg("circle")); | |
fore.setAttribute("fill", "none"); | |
fore.setAttribute("class", "fore"); | |
fore.setAttribute("r", r); | |
} else { | |
panContainer = null; | |
} | |
if (zoomStyle != "none") { | |
zoomContainer = g.appendChild(ms.svg("g")); | |
zoomContainer.setAttribute("class", "zoom"); | |
var j = -0.5; | |
if (zoomStyle == "big") { | |
ticks = {}; | |
for (var i = map.zoomRange()[0], j = 0; i <= map.zoomRange()[1]; i++, j++) { | |
(ticks[i] = zoomContainer.appendChild(tick(i))) | |
.setAttribute("transform", "translate(0," + (-(j + 0.75) * r * 0.4) + ")"); | |
} | |
} | |
var p = panStyle == "none" ? 0.4 : 2; | |
zoomContainer.setAttribute("transform", "translate(0," + r * (/^top-/.test(position) ? (p + (j + 0.5) * 0.4) : -p) + ")"); | |
zoomContainer.appendChild(zoom(+1)).setAttribute("transform", "translate(0," + (-(j + 0.5) * r * 0.4) + ")"); | |
zoomContainer.appendChild(zoom(-1)).setAttribute("transform", "scale(-1)"); | |
} else { | |
zoomContainer = null; | |
} | |
move(); | |
} | |
compass.radius = function(x) { | |
if (!arguments.length) return r; | |
r = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.speed = function(x) { | |
if (!arguments.length) return r; | |
speed = x; | |
return compass; | |
}; | |
compass.position = function(x) { | |
if (!arguments.length) return position; | |
position = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.pan = function(x) { | |
if (!arguments.length) return panStyle; | |
panStyle = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.zoom = function(x) { | |
if (!arguments.length) return zoomStyle; | |
zoomStyle = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("mousedown", mousedown, false); | |
container.removeChild(g); | |
container = null; | |
window.removeEventListener("mousemove", mousemove, false); | |
window.removeEventListener("mouseup", mouseup, false); | |
window = null; | |
map.off("move", move).off("resize", move); | |
} | |
if (map = x) { | |
container = map.svgContainer(); | |
container.appendChild(g); | |
container.addEventListener("mousedown", mousedown, false); | |
window = container.ownerDocument.defaultView; | |
window.addEventListener("mousemove", mousemove, false); | |
window.addEventListener("mouseup", mouseup, false); | |
map.on("move", move).on("resize", move); | |
draw(); | |
} | |
return compass; | |
}; | |
return compass; | |
}; | |
ms.grid = function() { | |
var grid = {}, | |
map, | |
g = ms.svg("g"); | |
g.setAttribute("class", "grid"); | |
function move(e) { | |
var p, | |
line = g.firstChild, | |
size = map.size(), | |
nw = map.pointLocation(zero), | |
se = map.pointLocation(size), | |
step = Math.pow(2, 4 - Math.round(map.zoom())); | |
// Round to step. | |
nw.lat = Math.floor(nw.lat / step) * step; | |
nw.lon = Math.ceil(nw.lon / step) * step; | |
// Longitude ticks. | |
for (var x; (x = map.locationPoint(nw).x) <= size.x; nw.lon += step) { | |
if (!line) line = g.appendChild(ms.svg("line")); | |
line.setAttribute("x1", x); | |
line.setAttribute("x2", x); | |
line.setAttribute("y1", 0); | |
line.setAttribute("y2", size.y); | |
line = line.nextSibling; | |
} | |
// Latitude ticks. | |
for (var y; (y = map.locationPoint(nw).y) <= size.y; nw.lat -= step) { | |
if (!line) line = g.appendChild(ms.svg("line")); | |
line.setAttribute("y1", y); | |
line.setAttribute("y2", y); | |
line.setAttribute("x1", 0); | |
line.setAttribute("x2", size.x); | |
line = line.nextSibling; | |
} | |
// Remove extra ticks. | |
while (line) { | |
var next = line.nextSibling; | |
g.removeChild(line); | |
line = next; | |
} | |
} | |
grid.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
g.parentNode.removeChild(g); | |
map.off("move", move).off("resize", move); | |
} | |
if (map = x) { | |
map.on("move", move).on("resize", move); | |
map.svgContainer().appendChild(g); | |
map.dispatch({type: "move"}); | |
} | |
return grid; | |
}; | |
return grid; | |
}; | |
ms.attribution = function(html) { | |
var attribution = {}, | |
map, | |
container = document.createElement("div"); | |
container.setAttribute("class", "mapsense-attribution"); | |
attribution.container = function() { | |
return container; | |
}; | |
attribution.html = function(x) { | |
if (!arguments.length) return container.innerHTML; | |
container.innerHTML = x; | |
return attribution; | |
}; | |
attribution.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
if (map === x) { | |
container.parentNode.appendChild(container); | |
return attribution; | |
} | |
container.parentNode.removeChild(container); | |
} | |
map = x; | |
if (map) { | |
map.relativeContainer().appendChild(container); | |
} | |
return attribution; | |
}; | |
return attribution.html(html); | |
}; | |
ms.basemap = function() { | |
var basemap = ms.topoJson(); | |
var attribution = ms.attribution('<a target="_blank" href="https://developer.mapsense.co/tileViewer/?tileset=mapsense.earth">©Mapsense ©OpenStreetMap</a>'); | |
var url = "https://{S}-api.mapsense.co/universes/mapsense.earth/{Z}/{X}/{Y}.topojson?s=10&ringSpan=8"; | |
var apiKey; | |
var style; | |
function urlWithKey() { | |
return ms.url(url + "&api-key=" + apiKey) | |
.hosts(["a", "b", "c", "d"]); | |
} | |
var __map__ = basemap.map; | |
basemap.map = function(x) { | |
var result = __map__.apply(basemap, arguments); | |
if (arguments.length) | |
attribution.map(x); | |
return result; | |
}; | |
var __url__ = basemap.url; | |
basemap.url = function(x) { | |
if (!arguments.length) return url; | |
url = x; | |
__url__.call(basemap, urlWithKey()); | |
return basemap; | |
}; | |
basemap.apiKey = function(x) { | |
if (!arguments.length) return apiKey; | |
apiKey = x; | |
__url__.call(basemap, urlWithKey()); | |
return basemap; | |
}; | |
basemap.style = function(x) { | |
if (!arguments.length) return style; | |
if (style) | |
attribution.container().classList.remove(style); | |
style = x; | |
basemap.selection(function(s) { | |
var styleClass = style ? "mapsense-" + style : ""; | |
var zoomClass = "_" + Math.floor(basemap.map().zoom()); | |
s.attr("class", function(feature) { | |
var classes = [ styleClass, zoomClass ]; | |
if (feature.properties) { | |
if (feature.properties.layer) | |
classes.push(feature.properties.layer); | |
if (feature.properties.sub_layer) | |
classes.push(feature.properties.sub_layer); | |
} | |
return classes.join(" "); | |
}); | |
}); | |
if (style) | |
attribution.container().classList.add(style); | |
return basemap; | |
}; | |
basemap.on("load", function(e) { | |
var g = e.tile.element; | |
var tileBackground = g.querySelector(".tile-background"); | |
if (tileBackground && style) | |
tileBackground.classList.add("mapsense-" + style); | |
}); | |
basemap.style("light"); | |
basemap.tileBackground(true); | |
basemap.clip(true); | |
return basemap; | |
}; | |
ms.stylist = function() { | |
var attrs = [], | |
styles = [], | |
title; | |
function stylist(e) { | |
var ne = e.features.length, | |
na = attrs.length, | |
ns = styles.length, | |
f, // feature | |
d, // data | |
o, // element | |
x, // attr or style or title descriptor | |
v, // attr or style or title value | |
i, | |
j; | |
for (i = 0; i < ne; ++i) { | |
if (!(o = (f = e.features[i]).element)) continue; | |
d = f.data; | |
for (j = 0; j < na; ++j) { | |
v = (x = attrs[j]).value; | |
if (typeof v === "function") v = v.call(null, d); | |
if (v == null) { | |
if (x.name.local) | |
o.removeAttributeNS(x.name.space, x.name.local); | |
else | |
o.removeAttribute(x.name); | |
} | |
else { | |
if (x.name.local) | |
o.setAttributeNS(x.name.space, x.name.local, v); | |
else | |
o.setAttribute(x.name, v); | |
} | |
} | |
for (j = 0; j < ns; ++j) { | |
v = (x = styles[j]).value; | |
if (typeof v === "function") v = v.call(null, d); | |
if (v == null) | |
o.style.removeProperty(x.name); | |
else | |
o.style.setProperty(x.name, v, x.priority); | |
} | |
if (v = title) { | |
if (typeof v === "function") v = v.call(null, d); | |
while (o.lastChild) o.removeChild(o.lastChild); | |
if (v != null) o.appendChild(ms.svg("title")).appendChild(document.createTextNode(v)); | |
} | |
} | |
} | |
stylist.attr = function(n, v) { | |
attrs.push({name: ns(n), value: v}); | |
return stylist; | |
}; | |
stylist.style = function(n, v, p) { | |
styles.push({name: n, value: v, priority: arguments.length < 3 ? null : p}); | |
return stylist; | |
}; | |
stylist.title = function(v) { | |
title = v; | |
return stylist; | |
}; | |
return stylist; | |
}; | |
if (typeof define === "function" && define.amd) | |
define(mapsense); | |
else if (typeof module === "object" && module.exports) | |
module.exports = mapsense; | |
this.mapsense = mapsense; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment