Skip to content

Instantly share code, notes, and snippets.

@jhnklly
Last active March 22, 2016 22:17
Show Gist options
  • Save jhnklly/c087666a0b0fde8673f5 to your computer and use it in GitHub Desktop.
Save jhnklly/c087666a0b0fde8673f5 to your computer and use it in GitHub Desktop.
Drag & drop csv to map
// Hat tip Mike Bostock: https://github.com/mbostock/polymaps/blob/master/examples/bounds/bounds.js
function bounds(features) {
var i = -1,
n = features.length,
geometry,
bounds = [{lon: Infinity, lat: Infinity}, {lon: -Infinity, lat: -Infinity}];
while (++i < n) {
//geometry = features[i].data.geometry;
geometry = features[i].geometry;
boundGeometry[geometry.type](bounds, geometry.coordinates);
}
return bounds;
}
function boundPoint(bounds, coordinate) {
var x = coordinate[0], y = coordinate[1];
if (x < bounds[0].lon) bounds[0].lon = x;
if (x > bounds[1].lon) bounds[1].lon = x;
if (y < bounds[0].lat) bounds[0].lat = y;
if (y > bounds[1].lat) bounds[1].lat = y;
}
function boundPoints(bounds, coordinates) {
var i = -1, n = coordinates.length;
while (++i < n) boundPoint(bounds, coordinates[i]);
}
function boundMultiPoints(bounds, coordinates) {
var i = -1, n = coordinates.length;
while (++i < n) boundPoints(bounds, coordinates[i]);
}
var boundGeometry = {
Point: boundPoint,
MultiPoint: boundPoints,
LineString: boundPoints,
MultiLineString: boundMultiPoints,
Polygon: function(bounds, coordinates) {
boundPoints(bounds, coordinates[0]); // exterior ring
},
MultiPolygon: function(bounds, coordinates) {
var i = -1, n = coordinates.length;
while (++i < n) boundPoints(bounds, coordinates[i][0]);
}
};
document.addEventListener("DOMContentLoaded", function(event) {
var dropzone = document.querySelector('#myMap');
var dragzone = document.querySelector('#myMap');
var filelist;
dropzone.addEventListener('drop', function(event) {
if (event.preventDefault) {
event.preventDefault();
}
var types = event.dataTransfer.types;
filelist = event.dataTransfer.files;
var file = event.dataTransfer.files[0];
if (file.type !== 'text/csv') {
alert('Uploaded file must be a RFC4180-compliant CSV file');
return false;
}
// Init file reader
var fileReader = new FileReader();
fileReader.onload = function (e) {
processCsv(d3.csv.parse(fileReader.result));
};
fileReader.onerror = function (e) {
throw 'Error reading CSV file';
};
// Start reading file
fileReader.readAsText(file);
return false;
}, false);
dragzone.addEventListener('dragstart', function(event) {
return true;
}, true);
dragzone.addEventListener('dragend', function(event) {
return true;
}, true);
dropzone.addEventListener('dragenter', function(event) {
if (event.preventDefault) event.preventDefault();
return false;
}, false);
dropzone.addEventListener('dragover', function(event) {
if (event.preventDefault) event.preventDefault(); // allows us to drop
}, false);
dropzone.addEventListener('dragleave', function(event) {
if (event.preventDefault) event.preventDefault(); // allows us to drop
return false;
}, false);
});
We can make this file beautiful and searchable if this error is corrected: It looks like row 19 should actually have 7 columns, instead of 1. in line 18.
"INPUT","lat","lon","display_name","class","type","importance"
"Acadia National Park","44.28969315","-68.397779592087","Acadia National Park, Audrey's Lane, Seal Cove, Hancock County, Maine, United States of America","leisure","park","0.71914108746201"
"Arches National Park","38.727356","-109.562989344458","Arches National Park, Arches Hiking, Grand County, Utah, United States of America","leisure","nature_reserve","0.8819353381181"
"Biscayne National Park","25.50312235","-80.2348495319876","Biscayne National Park, Miami-Dade County, Florida, United States of America","leisure","nature_reserve","0.75449074108058"
"Badlands National Park","43.52710585","-102.361662795674","Badlands National Park, Oglala Lakota County, South Dakota, United States of America","leisure","park","0.745262970359"
"Big Bend National Park","29.33341","-103.194000319734","Big Bend National Park, Panther Junction, Brewster County, Texas, 79834, United States of America","leisure","park","0.84579082541219"
"Black Canyon of the Gunnison National Park","38.5798065","-107.743696949741","Black Canyon of the Gunnison National Park, Warner Route, Montrose County, Colorado, United States of America","leisure","park","1.1235546632572"
"Bryce Canyon National Park","37.5713588","-112.185656507705","Bryce Canyon National Park, Under the Rim Trail, Garfield County, Utah, United States of America","leisure","nature_reserve","0.87468092975348"
"Capitol Reef National Park","38.06702855","-111.155256098546","Capitol Reef National Park, Garfield County, Utah, United States of America","leisure","nature_reserve","0.98019506526221"
"Canyonlands National Park","38.1666522","-109.984017","Canyonlands National Park, NP 756, Garfield County, Utah, United States of America","leisure","park","0.301"
"Carlsbad Caverns National Park","32.11926425","-104.600826706786","Carlsbad Caverns National Park, Slaughter Canyon Cave Trail, Eddy County, New Mexico, United States of America","leisure","nature_reserve","0.83445767985789"
"Congaree National Park","33.8302322","-80.8243399","Congaree National Park, Caroline Sims Road, Weston, Richland County, South Carolina, 29061, United States of America","leisure","park","0.68844676960817"
"Crater Lake National Park","42.931692","-122.127836406071","Crater Lake National Park, Wizard Island Summit Trail, Rim Village, Klamath County, Oregon, United States of America","leisure","nature_reserve","0.85379433483971"
"Channel Islands National Park","34.0083586","-119.3957895","Channel Islands National Park, Ventura, California, United States of America","leisure","park","0.401"
"Cuyahoga Valley National Park","41.2608304","-81.556429320078","Cuyahoga Valley National Park, Ohio & Erie Canal Towpath Trail, Boston, Boston Township, Summit County, Ohio, 44264, United States of America","leisure","nature_reserve","0.8108263210417"
"Death Valley National Park","36.490621","-117.102029074319","Death Valley National Park, Inyo County, California, United States of America","leisure","nature_reserve","0.8931891212183"
"Dry Tortugas National Park","24.6377223","-82.8767980601244","Dry Tortugas National Park, Monroe County, Florida, United States of America","leisure","park","0.84097789671849"
"Everglades National Park","25.3870275","-80.8802630659629","Everglades National Park, Monroe County, Florida, United States of America","boundary","national_park","0.77882407233632"
"Not found: Gates of the Arctic National Park"
"Glacier National Park","48.61691115","-113.760879457289","Glacier National Park, Sperry Chalets, Flathead County, Montana, United States of America","leisure","nature_reserve","0.78894916366889"
"Grand Teton National Park","43.810789","-110.648591941638","Grand Teton National Park, Jackson Hole, Teton County, Wyoming, United States of America","leisure","nature_reserve","0.88206595135262"
"Great Basin National Park","38.915135","-114.1869029","Great Basin National Park, White Pine County, Nevada, 89311, United States of America","highway","track","0.81271729740322"
"Grand Canyon National Park","36.3044354","-112.29004113377","Grand Canyon National Park, Coconino County, Arizona, United States of America","leisure","nature_reserve","0.87271502047963"
"Denali National Park & Preserve","63.1831737","-151.148363066604","Denali National Park and Preserve, Denali, Alaska, United States of America","leisure","nature_reserve","0.72715694630872"
"Great Sand Dunes National Park and Preserve","37.8092835","-105.581283091798","Great Sand Dunes National Park and Preserve, Saguache County, Colorado, United States of America","leisure","park","1.1214471424681"
"Great Smoky Mountains National Park","35.6079063","-83.5021791546683","Great Smoky Mountains National Park, Fort Harry, Sevier County, Tennessee, United States of America","leisure","park","0.96059389488672"
"Not found: Haleakala National Park"
"Guadalupe Mountains National Park","31.90828235","-104.900320000159","Guadalupe Mountains National Park, Kincaid Trail, Culberson County, Texas, United States of America","leisure","nature_reserve","0.8111207303807"
"Hawaii Volcanoes National Park","19.3031881","-155.240502021651","Hawai'i Volcanoes National Park, Hilo, Hawaiʻi County, Hawaii, United States of America","leisure","park","0.61"
"Hot Springs National Park","34.5167562","-93.0529572","Hot Springs National Park, Fountain Street, Hot Springs, Garland County, Arkansas, 71901, United States of America","leisure","park","0.80481108502374"
"Kenai Fjords National Park","59.311314","-150.7197276","Kenai Fjords National Park, Kenai Peninsula, Alaska, United States of America","leisure","nature_reserve","0.401"
"Kings Canyon National Park","36.91527845","-118.607643757552","Kings Canyon National Park, Fresno County, California, United States of America","leisure","nature_reserve","0.85837881165037"
"Joshua Tree National Park","33.89556375","-115.94731898683","Joshua Tree National Park, Riverside County, California, United States of America","leisure","nature_reserve","0.8630099734438"
"Katmai National Park and Preserve","58.6929377","-156.667593","Katmai National Park and Preserve Headquarters, Boris Boulevard, King Salmon, Bristol Bay, Alaska, 99613, United States of America","tourism","information","0.501"
"Isle Royale National Park","48.0020629","-88.8411052","Isle Royale National Park, Eagle Harbor Township, Keweenaw, Michigan, United States of America","leisure","park","0.401"
"Not found: Kobuk Valley National Park"
"Lassen Volcanic National Park","40.4915974","-121.404609417303","Lassen Volcanic National Park, Lassen Peak Highway, Shasta County, California, United States of America","leisure","nature_reserve","0.85837881165037"
"Mammoth Cave National Park","37.1841693","-86.1229909291288","Mammoth Cave National Park, Green River Ferry Road, Mammoth Cave, Edmonson County, Kentucky, United States of America","leisure","park","0.8371023488262"
"Mesa Verde National Park","37.253776","-108.455596309289","Mesa Verde National Park, Moccasin Mesa Road, Montezuma County, Colorado, United States of America","leisure","park","0.89914501564847"
"Mount Rainier National Park","46.8540872","-121.706319447911","Mount Rainier National Park, United States of America","boundary","national_park","0.85222040055854"
"Olympic National Park","47.656878","-124.3800579","Olympic National Park, US 101, Jefferson County, Washington, United States of America","leisure","nature_reserve","0.75837881165037"
"National Park of American Samoa","-14.2619444","-170.6880556","National Park of American Samoa, Route 001, Alega, Sua, American Samoa, United States of America","leisure","park","0.86704474917533"
"North Cascades National Park","48.5277956","-120.997116604024","North Cascades National Park, Skagit County, Washington, United States of America","leisure","nature_reserve","0.83213550665887"
"Petrified Forest National Park","34.9744769","-109.707994237463","Petrified Forest National Park, Apache County, Arizona, United States of America","boundary","national_park","0.84041063794806"
"Redwood National Park","41.46176695","-124.041044510018","Redwood National Park, Newton B. Drury Scenic Parkway, Humboldt County, California, United States of America","leisure","park","0.77289081775607"
"Saguaro National Park","32.2786864","-111.1826004","Saguaro National Park, Dobe Wash Trail, Pima County, Arizona, United States of America","leisure","park","0.301"
"Rocky Mountain National Park","40.355488","-105.717805293638","Rocky Mountain National Park, Larimer County, Colorado, United States of America","leisure","park","0.85786835123079"
"Sequoia National Park","36.5657034","-118.7730015","Sequoia National Park, Pinewood, Tulare County, California, 93262, United States of America","leisure","nature_reserve","0.76872621020709"
"Shenandoah National Park","38.475313","-78.4547072804979","Shenandoah National Park, Appalachian Trail, Greene County, Virginia, United States of America","leisure","nature_reserve","0.73424986166809"
"Theodore Roosevelt National Park","47.5889496","-103.372036266479","Theodore Roosevelt National Park, Achenbach Trail, McKenzie County, North Dakota, United States of America","leisure","nature_reserve","0.80978559908338"
"Voyageurs National Park","48.446194","-93.0304329","Voyageurs National Park, Kabetogama, Saint Louis County, Minnesota, United States of America","highway","residential","0.4"
"Virgin Islands National Park","18.33688305","-64.740011739034","Virgin Islands National Park, United States of America","boundary","national_park","0.79146640414423"
"Wind Cave National Park","43.5789475","-103.470656720908","Wind Cave National Park, State Highway 87, Custer County, South Dakota, United States of America","leisure","park","0.79784622341982"
"Yellowstone National Park","44.766607","-110.2340824","Yellowstone National Park, Park County, Wyoming, United States of America","leisure","nature_reserve","0.8577723806647"
"Yosemite National Park","37.8406798","-119.516626169942","Yosemite National Park, Mariposa County, California, United States of America","leisure","nature_reserve","0.83563787641991"
"Not found: Wrangell – St. Elias National Park and Preserve"
"Zion National Park","37.3190845","-113.004952668628","Zion National Park, West Rim Trail, Washington County, Utah, United States of America","leisure","nature_reserve","0.77519182185623"
<!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://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" media="screen" type="text/css">
<!--
<script src="saveSvgAsPng.js" charset="utf-8"></script>
<link rel="stylesheet" href="nocss.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;
}
circle {
fill: rgba(68, 167, 228, 0.5); /*blue*/
stroke: none;
}
.faint { color: #555; }
.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;
}
.csv_point,
circle {
/* purple
fill: rgba(152, 78, 163, 0.2);
stroke: rgba(152, 78, 163, 1);
*/
fill: rgba(68, 167, 228, 0.5);
stroke: rgba(68, 167, 228, 1); /*blue*/
stroke-width: 1;
}
.modal-dialog {
width: 400px;
text-align: center;
font-size: 20px;
}
.modal-body {
margin: 0 auto;
text-align: right;
width: 270px;
}
.lede {
position: absolute;
top: 0;
left: 0;
padding: 5px 10px;
background: rgba(255,255,255,0.5);
width: 100%;
}
.mapsense-attribution {
/* adjusted for less contrast */
background-color: rgba(255, 255, 255, 0.5);
color: #777;
position: absolute;
bottom: 0;
right: 0;
font-size: 12px;
font-family: sans-serif;
padding: 2px;
line-height: 1.2em;
text-decoration: none;
user-select: none;
cursor: default;
}
.mapsense-attribution a {
text-decoration: none;
color: #777;
}
.mapsense-attribution a:hover {
text-decoration: none;
color: #444;
}
.hoverFeature {
stroke-width: 2;
}
.popup {
position: absolute;
font-family: sans-serif;
font-size: 11px;
color: #666;
visibility: hidden;
border-radius: 3px;
pointer-events: none;
border: 1px solid #bbb;
padding: 8px;
background-color: rgba(255, 255, 255, .95);
max-height: 500px;
overflow: normal;
}
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid #ddd;
padding: 7px;
}
.detailKey {
background: #eee;
opacity: .8;
text-transform: uppercase;
font-weight: 600;
}
.detailVal {
background: rgba(255,255,255,0.8);
}
</style>
</head>
<body>
<div id="myMap"></div>
<div class="lede">
<div class="lede">
<p>Have a csv with latitude and longitude columns?
<br/>Drag it onto the map!</p>
<p class="faint">
No csv file handy? <a href="sf_hills.csv">Sample here.</a>
<br>Or <a href="#" id="justLoadIt">just load it</a>.
<br><a href="#" id="csv2geojson">Save as geojson</a>.
</p>
</div>
<!-- Modal -->
<div id="fieldModal" class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Pick fields</h4>
</div>
<div id="pickfields" class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal">Close</button>
<button type="button" class="btn" id="fields_picked">Map!</button>
</div>
</div>
</div>
</div>
<!-- /Modal -->
<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 src='dragdrop_csv.js' type="text/javascript"></script>
<script src='bounds.js' type="text/javascript"></script>
<script>
var A = {};
A.geoJson = {
type: "FeatureCollection",
features: []
}; //init a geojson object
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 map = mapsense.map("#myMap"); //tell it where to go
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.extent(home);
//map.center({lon: -90, lat: 37.75})
// .zoom(4);
map.tileSize({x:256,y:256});
map.add(mapsense.hash());
map.add(imagery_layer.visible(true).id("imagery_layer"));
map.add(labels_layer.visible(true).id("labels_layer"));
d3.select("#imagery_layer").attr("style","opacity: 0.5;");
d3.select("#labels_layer").attr("style","opacity: 0.5;");
document.querySelector('.mapsense-map').setAttribute('id','mapsense-map');
// add id for:
// saveSvgAsPng(document.getElementById("mapsense-map"), "drag_csv.png");
var credit = '<a href="https://openstreetmap.org/about/" target="_blank">© OpenStreetMap</a> | <a target="_blank" href="http://stamen.com">Stamen Design</a>';
credit += ' | <a href="http://www.mapquest.com/" target="_blank">MapQuest</a>';
//credit += '<img src="http://developer.mapquest.com/content/osm/mq_logo.png">';
// Add custom attribution
var attribution = mapsense.attribution(credit);
attribution.map(map);
var CSV_LAYER;
// Process CSV
var processCsv = function(csvData) {
console.log(csvData);
var first_line = csvData[0];
// If no lat, lon fields, then prompt user to pick lat/lon fields:
var latfield, lonfield, sizefield, labelfield;
//TODO:
sizefield = 'ft' || 'value' || 'size' || 'ft';
labelfield = 'label' || 'name' || 'Input';
console.log(sizefield,labelfield);
// TODO: make this fuzzy match better
if (first_line.lat && first_line.lon) {
latfield = 'lat';
lonfield = 'lon';
console.log(latfield, lonfield, sizefield, labelfield);
mapCSV(csvData, latfield, lonfield, sizefield, labelfield);
} else if (first_line.latitude && first_line.longitude) {
latfield = 'latitude';
lonfield = 'longitude';
mapCSV(csvData, latfield, lonfield, sizefield, labelfield);
} else { // otherwise prompt user
var options = '<option value=""></option>';
for (key in first_line) {
options += '<option value="' + key + '">' + key + '</option>';
}
var pickfields = '';
pickfields += '<div>Location <select id="locfield">' + options + '</select></div>';
pickfields += '<div>Latitude: <select id="latfield">' + options + '</select></div>';
pickfields += '<div>Longitude: <select id="lonfield">' + options + '</select></div>';
pickfields += '<div>Size: <select id="sizefield">' + options + '</select></div>';
pickfields += '<div style="display: none;">Label: <select id="labelfield">' + options + '</select></div>';
$('#pickfields').html(pickfields);
$('#fieldModal').modal('show');
var guess_loc = ['zip', 'city', 'name', 'location', 'address'];
for (var i = 0; i < guess_loc.length; i++) {
var field = first_line[guess_loc[i]];
if (guess_loc.indexOf(field) >= 0) {
locfield = field;
}
}
if (first_line.y) locfield = 'y';
if (first_line.lat) locfield = 'lat';
if (first_line.latitude) locfield = 'latitude';
if (locfield) $('#locfield').val(locfield);
if (first_line.y) latfield = 'y';
if (first_line.lat) latfield = 'lat';
if (first_line.latitude) latfield = 'latitude';
if (latfield) $('#latfield').val(latfield);
if (first_line.x) lonfield = 'x';
if (first_line.lng) lonfield = 'lng';
if (first_line.lon) lonfield = 'lon';
if (first_line.longitude) lonfield = 'longitude';
if (lonfield) $('#lonfield').val(lonfield);
$('#fields_picked').click(function() {
latfield = $('#latfield').val();
lonfield = $('#lonfield').val();
sizefield = $('#sizefield').val();
labelfield = $('#labelfield').val();
$('#fieldModal').modal('hide');
mapCSV(csvData, latfield, lonfield, sizefield, labelfield);
});
}
};
function mapCSV(data, lat, lon, size, label) {
console.log(size, label);
var gid = 0;
var max = -Infinity;
var min = Infinity;
for (var key in data) {
if (+data[key][size] > max) max = +data[key][size];
if (+data[key][size] < min) min = +data[key][size];
}
for (var key in data) {
gid += 1;
var radius = 10;
if (+data[key][size]) {
// convert value to percent(ish)
// and add 10 so visible
var s = +data[key][size];
radius = 5 + (20 * s / max);
console.log(s, radius, max);
}
data[key].label = data[key][label] || "name";
data[key].radius = radius;
data[key].gid = gid;
var feature = {
type: "Feature",
geometry: {
type: "Point",
"coordinates": [+data[key][lon], +data[key][lat]]
},
/*properties: {
label: data[key][label] || "name",
radius: radius
}*/
properties: data[key]
};
A.geoJson.features.push(feature);
}
CSV_LAYER = mapsense.geoJson()
.features(A.geoJson.features)
/*.selection(function(d){
d.attr("r", function(d){
console.log(d.properties.radius);
return d.properties.radius;
});
d.attr("class","csv_point");
d.attr("id",function(d){
return d.properties.label ? d.properties.label : "";
});
})*/
.selection(selection_function);
map.add(CSV_LAYER);
//.on("load", zoomBounds)
zoomBounds(A.geoJson);
}
function zoomBounds(e) {
map.extent(bounds(e.features)).zoomBy(-0.1);
}
var tooltip = d3.select('body')
.append("div")
.attr("class", "popup");
function isFloat(n) {
var r = Number(n) && n % 1 !== 0;
return r;
}
var selection_function = (function() {
var whitelist = [];
var id_field = 'gid';
//whitelist = ['population','female','male','hhi','layer','poverty','unemployment','uninsured','postal_code'];
/* The id of the previously hovered-over feature. */
var hoverId = null;
return function(s) {
s.attr("r", function(d) {
return d.properties.radius || 10;
})
.attr("class", function(d) {
var classes = ["mapFeatures"],
props = d.properties;
if (props) {
if (props.layer) classes.push(props.layer);
if (props.sub_layer) classes.push(props.sub_layer);
}
if (d.properties[id_field] !== null && hoverId !== null && d.properties[id_field] == hoverId) {
console.log(d.properties[id_field], hoverId);
classes.push("hoverFeature");
}
return classes.join(' ');
})
.on("mouseover", function(d) {
console.log(d);
//if ( d.properties.minz >= 5 ) {
d3.select(this).classed("selected", true);
//}
var text = "";
var value;
text += '<div class="detailCard"><table><tbody>';
if (whitelist.length > 0) {
for (var i = 0; i < whitelist.length; i++) {
key = whitelist[i];
if (d.properties && d.properties[key]) {
value = d.properties[key];
value = isFloat(value) ? value.toFixed(4) : value;
text += '<tr><td class="detailKey">' + key + '</td><td class="detailVal">' + value + '</td></tr>';
}
}
} else {
for (var key in d.properties) {
text += '<tr><td class="detailKey">' + key + '</td><td class="detailVal">' + d.properties[key] + '</td></tr>';
}
}
tooltip.html(text);
if (d.properties[id_field] !== hoverId) {
hoverId = d.properties[id_field];
CSV_LAYER.selection(selection_function);
}
return tooltip.style("visibility", "visible");
})
.on("mousemove", function() {
return tooltip.style("top", (d3.event.pageY - 0) + "px").style("left", (d3.event.pageX + 20) + "px");
})
.on("mouseout", function(d) {
d3.select(this).classed("selected", false);
return tooltip.style("visibility", "hidden");
});
};
})();
d3.select('#justLoadIt')
.on('click', function(){
d3.csv('sf_hills.csv', function(error, data) {
processCsv(data);
});
})
;
d3.select('#csv2geojson')
.on('click', function(){
var wnd = window.open("about:blank", "", "_blank");
wnd.document.write( JSON.stringify(A.geoJson) );
})
;
</script>
</body>
</html>
.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%;
}
(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 attribution = ms.attribution('<a href="https://openstreetmap.org/about/" target="_blank">© 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;
})();
(function() {
var out$ = typeof exports != 'undefined' && exports || this;
var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
function isExternal(url) {
return url && url.lastIndexOf('http',0) == 0 && url.lastIndexOf(window.location.host) == -1;
}
function inlineImages(el, callback) {
var images = el.querySelectorAll('image');
var left = images.length;
if (left == 0) {
callback();
}
for (var i = 0; i < images.length; i++) {
(function(image) {
var href = image.getAttributeNS("http://www.w3.org/1999/xlink", "href");
if (href) {
if (isExternal(href.value)) {
console.warn("Cannot render embedded images linking to external hosts: "+href.value);
return;
}
}
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var img = new Image();
href = href || image.getAttribute('href');
img.src = href;
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
image.setAttributeNS("http://www.w3.org/1999/xlink", "href", canvas.toDataURL('image/png'));
left--;
if (left == 0) {
callback();
}
}
img.onerror = function() {
console.log("Could not load "+href);
left--;
if (left == 0) {
callback();
}
}
})(images[i]);
}
}
function styles(el, selectorRemap) {
var css = "";
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
if (isExternal(sheets[i].href)) {
console.warn("Cannot include styles from other hosts: "+sheets[i].href);
continue;
}
var rules = sheets[i].cssRules;
if (rules != null) {
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
if (typeof(rule.style) != "undefined") {
var match = null;
try {
match = el.querySelector(rule.selectorText);
} catch(err) {
console.warn('Invalid CSS selector "' + rule.selectorText + '"', err);
}
if (match) {
var selector = selectorRemap ? selectorRemap(rule.selectorText) : rule.selectorText;
css += selector + " { " + rule.style.cssText + " }\n";
} else if(rule.cssText.match(/^@font-face/)) {
css += rule.cssText + '\n';
}
}
}
}
}
return css;
}
function getDimension(el, clone, dim) {
return (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
el.getBoundingClientRect()[dim] ||
parseInt(clone.style[dim]) ||
parseInt(window.getComputedStyle(el).getPropertyValue(dim));
}
out$.svgAsDataUri = function(el, options, cb) {
options = options || {};
options.scale = options.scale || 1;
var xmlns = "http://www.w3.org/2000/xmlns/";
inlineImages(el, function() {
var outer = document.createElement("div");
var clone = el.cloneNode(true);
var width, height, viewBoxWidth, viewBoxHeight;
if(el.tagName == 'svg') {
width = getDimension(el, clone, 'width');
height = getDimension(el, clone, 'height');
if (typeof width === "undefined" || width === null || isNaN(parseFloat(width))) {
width = 0;
}
if (typeof height === "undefined" || height === null || isNaN(parseFloat(height))) {
height = 0;
}
viewBoxWidth = el.viewBox.baseVal && el.viewBox.baseVal.width !== 0 ? el.viewBox.baseVal.width : width;
viewBoxHeight = el.viewBox.baseVal && el.viewBox.baseVal.height !== 0 ? el.viewBox.baseVal.height : height;
} else if(el.getBBox) {
var box = el.getBBox();
width = box.x + box.width;
height = box.y + box.height;
clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
viewBoxWidth = width;
viewBoxHeight = height;
var svg = document.createElementNS('http://www.w3.org/2000/svg','svg')
svg.appendChild(clone)
clone = svg;
} else {
console.error('Attempted to render non-SVG element', el);
return;
}
clone.setAttribute("version", "1.1");
clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
clone.setAttribute("width", width * options.scale);
clone.setAttribute("height", height * options.scale);
clone.setAttribute("viewBox", "0 0 " + viewBoxWidth + " " + viewBoxHeight);
outer.appendChild(clone);
var css = styles(el, options.selectorRemap);
var s = document.createElement('style');
s.setAttribute('type', 'text/css');
s.innerHTML = "<![CDATA[\n" + css + "\n]]>";
var defs = document.createElement('defs');
defs.appendChild(s);
clone.insertBefore(defs, clone.firstChild);
var svg = doctype + outer.innerHTML;
// encode then decode to handle `btoa` on Unicode; see MDN for `btoa`.
var uri = 'data:image/svg+xml;base64,' + window.btoa(decodeURIComponent(encodeURIComponent(svg)));
if (cb) {
cb(uri);
}
});
}
out$.svgAsPngUri = function(el, options, cb) {
out$.svgAsDataUri(el, options, function(uri) {
var image = new Image();
image.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
if(options && options.backgroundColor){
context.fillStyle = options.backgroundColor;
context.fillRect(0, 0, canvas.width, canvas.height);
}
context.drawImage(image, 0, 0);
var a = document.createElement('a');
cb(canvas.toDataURL('image/png'));
}
image.src = uri;
});
}
out$.saveSvgAsPng = function(el, name, options) {
options = options || {};
out$.svgAsPngUri(el, options, function(uri) {
var a = document.createElement('a');
a.download = name;
a.href = uri;
document.body.appendChild(a);
a.addEventListener("click", function(e) {
a.parentNode.removeChild(a);
});
a.click();
});
}
})();
Name ft view longitude latitude
Irish Hill 50 -122.38644 37.75882
Rincon Hill 100 -122.39194 37.78574
Mint Hill 150 -122.42726 37.7701
Cathedral Hill 200 -122.42508 37.78442
College Hill 200 -122.42709 37.73505
Sutro Heights 200 -122.51168 37.77794
Dolores Heights 216 -122.42486 37.75046
Alamo Hill 225 10 -122.43469 37.77584
Mt. St. Joseph 250 -122.39654 37.7338
Washington Heights 260 -122.49099 37.7774
University Mound 265 -122.41568 37.7248
Holly Hill 274 -122.41997 37.73735
Hunter's Point Ridge 275 13 -122.38336 37.73295
Telegraph Hill 284 3 -122.40593 37.8028
Russian Hill North Peak 300 -122.41967 37.80105
Russian Hill South Peak 300 -122.4141 37.79789
Portrero Hill 300 -122.39842 37.75585
Excelsior Heights 315 -122.42426 37.723
Laurel Hill 320 -122.44808 37.78428
Anza Vista Hill 325 -122.4443 37.78031
City College Hill 350 -122.45089 37.7255
Pacific Heights 370 15 -122.44597 37.79188
Presidio Heights 370 -122.45537 37.78879
Presidio Hill 370 -122.46396 37.79361
Rob Hill 374 -122.47563 37.79541
Candlestick Hill 375 7 -122.39203 37.71459
Nob Hill 376 -122.41501 37.79348
Lafayette Heights 378 -122.42759 37.79163
Lincoln Heights 380 17 -122.50073 37.7835
Billy Goat Hill 400 18 -122.43304 37.74125
Liberty Hill 400 -122.43113 37.75575
Parnassus Heights 400 -122.45486 37.76213
Castro/Liberty Hill 407 -122.43561 37.75465
Strawberry Hill 412 19 -122.4755 37.76854
Lone Mountain 448 20 -122.45164 37.77896
Bernal Hill 500 4 -122.41524 37.74296
Merced Heights 500 8 -122.46267 37.71788
Corona Heights 510 6 -122.43936 37.765
McLaren Ridge 515 11 -122.4198 37.719
Glen Park Hill 525 -122.43216 37.73855
Tank Hill 560 14 -122.44769 37.75962
Buena Vista Hill 569 12 -122.44157 37.76781
Mt. Olympus 570 -122.44551 37.76329
Monterey Heights 575 -122.46057 37.73387
Grand View Park (Larsen Peak) 666 5 -122.4717 37.75621
Gold Mine Hill 679 -122.43726 37.74126
Red Rock Hill 689 -122.44155 37.74632
Mt. Parnassus 700 -122.45344 37.75942
Golden Gate Heights 720 -122.46881 37.74975
Edgehill Mountain 731 -122.45906 37.74262
Forest Hill 778 -122.46696 37.74802
Sherwood Forest Hill 825 -122.45629 37.73666
Clarendon Heights 850 -122.45279 37.7551
Eureka Peak (Twin Peaks North) 905 -122.44742 37.75336
Mt. Sutro 909 9 -122.45721 37.75816
Noe Peak (Twin Peaks South) 922 1 -122.44771 37.75159
Mt. Davidson 925 2 -122.45455 37.73825
Kite Hill 300 -122.44164 37.75816
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment