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
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "Name": "Irish Hill", "ft": 50, "view": null, "longitude": -122.386440, "latitude": 37.758820 }, "geometry": { "type": "Point", "coordinates": [ -122.386444, 37.758818 ] } },
{ "type": "Feature", "properties": { "Name": "Rincon Hill", "ft": 100, "view": null, "longitude": -122.391940, "latitude": 37.785740 }, "geometry": { "type": "Point", "coordinates": [ -122.391944, 37.785741 ] } },
{ "type": "Feature", "properties": { "Name": "Mint Hill", "ft": 150, "view": null, "longitude": -122.427260, "latitude": 37.770100 }, "geometry": { "type": "Point", "coordinates": [ -122.427263, 37.770104 ] } },
{ "type": "Feature", "properties": { "Name": "Cathedral Hill", "ft": 200, "view": null, "longitude": -122.425080, "latitude": 37.784420 }, "geometry": { "type": "Point", "coordinates": [ -122.425075, 37.784418 ] } },
{ "type": "Feature", "properties": { "Name": "College Hill", "ft": 200, "view": null, "longitude": -122.427090, "latitude": 37.735050 }, "geometry": { "type": "Point", "coordinates": [ -122.427092, 37.735053 ] } },
{ "type": "Feature", "properties": { "Name": "Sutro Heights", "ft": 200, "view": null, "longitude": -122.511680, "latitude": 37.777940 }, "geometry": { "type": "Point", "coordinates": [ -122.511678, 37.77794 ] } },
{ "type": "Feature", "properties": { "Name": "Dolores Heights", "ft": 216, "view": null, "longitude": -122.424860, "latitude": 37.750460 }, "geometry": { "type": "Point", "coordinates": [ -122.42486, 37.75046 ] } },
{ "type": "Feature", "properties": { "Name": "Alamo Hill", "ft": 225, "view": 10, "longitude": -122.434690, "latitude": 37.775840 }, "geometry": { "type": "Point", "coordinates": [ -122.434693, 37.77584 ] } },
{ "type": "Feature", "properties": { "Name": "Mt. St. Joseph", "ft": 250, "view": null, "longitude": -122.396540, "latitude": 37.733800 }, "geometry": { "type": "Point", "coordinates": [ -122.396536, 37.733797 ] } },
{ "type": "Feature", "properties": { "Name": "Washington Heights", "ft": 260, "view": null, "longitude": -122.490990, "latitude": 37.777400 }, "geometry": { "type": "Point", "coordinates": [ -122.490993, 37.777397 ] } },
{ "type": "Feature", "properties": { "Name": "University Mound", "ft": 265, "view": null, "longitude": -122.415680, "latitude": 37.724800 }, "geometry": { "type": "Point", "coordinates": [ -122.415676, 37.724802 ] } },
{ "type": "Feature", "properties": { "Name": "Holly Hill", "ft": 274, "view": null, "longitude": -122.419970, "latitude": 37.737350 }, "geometry": { "type": "Point", "coordinates": [ -122.419968, 37.737345 ] } },
{ "type": "Feature", "properties": { "Name": "Hunter's Point Ridge", "ft": 275, "view": 13, "longitude": -122.383360, "latitude": 37.732950 }, "geometry": { "type": "Point", "coordinates": [ -122.383361, 37.732949 ] } },
{ "type": "Feature", "properties": { "Name": "Telegraph Hill", "ft": 284, "view": 3, "longitude": -122.405930, "latitude": 37.802800 }, "geometry": { "type": "Point", "coordinates": [ -122.405934, 37.802799 ] } },
{ "type": "Feature", "properties": { "Name": "Russian Hill North Peak", "ft": 300, "view": null, "longitude": -122.419670, "latitude": 37.801050 }, "geometry": { "type": "Point", "coordinates": [ -122.419667, 37.801051 ] } },
{ "type": "Feature", "properties": { "Name": "Russian Hill South Peak", "ft": 300, "view": null, "longitude": -122.414100, "latitude": 37.797890 }, "geometry": { "type": "Point", "coordinates": [ -122.414096, 37.797891 ] } },
{ "type": "Feature", "properties": { "Name": "Portrero Hill", "ft": 300, "view": null, "longitude": -122.398420, "latitude": 37.755850 }, "geometry": { "type": "Point", "coordinates": [ -122.398424, 37.755855 ] } },
{ "type": "Feature", "properties": { "Name": "Excelsior Heights", "ft": 315, "view": null, "longitude": -122.424260, "latitude": 37.723000 }, "geometry": { "type": "Point", "coordinates": [ -122.424259, 37.723003 ] } },
{ "type": "Feature", "properties": { "Name": "Laurel Hill", "ft": 320, "view": null, "longitude": -122.448080, "latitude": 37.784280 }, "geometry": { "type": "Point", "coordinates": [ -122.448077, 37.784283 ] } },
{ "type": "Feature", "properties": { "Name": "Anza Vista Hill", "ft": 325, "view": null, "longitude": -122.444300, "latitude": 37.780310 }, "geometry": { "type": "Point", "coordinates": [ -122.444301, 37.780314 ] } },
{ "type": "Feature", "properties": { "Name": "City College Hill", "ft": 350, "view": null, "longitude": -122.450890, "latitude": 37.725500 }, "geometry": { "type": "Point", "coordinates": [ -122.450889, 37.725501 ] } },
{ "type": "Feature", "properties": { "Name": "Pacific Heights", "ft": 370, "view": 15, "longitude": -122.445970, "latitude": 37.791880 }, "geometry": { "type": "Point", "coordinates": [ -122.445974, 37.79188 ] } },
{ "type": "Feature", "properties": { "Name": "Presidio Heights", "ft": 370, "view": null, "longitude": -122.455370, "latitude": 37.788790 }, "geometry": { "type": "Point", "coordinates": [ -122.455373, 37.788794 ] } },
{ "type": "Feature", "properties": { "Name": "Presidio Hill", "ft": 370, "view": null, "longitude": -122.463960, "latitude": 37.793610 }, "geometry": { "type": "Point", "coordinates": [ -122.463956, 37.793609 ] } },
{ "type": "Feature", "properties": { "Name": "Rob Hill", "ft": 374, "view": null, "longitude": -122.475630, "latitude": 37.795410 }, "geometry": { "type": "Point", "coordinates": [ -122.475629, 37.795407 ] } },
{ "type": "Feature", "properties": { "Name": "Candlestick Hill", "ft": 375, "view": 7, "longitude": -122.392030, "latitude": 37.714590 }, "geometry": { "type": "Point", "coordinates": [ -122.392027, 37.714593 ] } },
{ "type": "Feature", "properties": { "Name": "Nob Hill", "ft": 376, "view": null, "longitude": -122.415010, "latitude": 37.793480 }, "geometry": { "type": "Point", "coordinates": [ -122.415014, 37.793475 ] } },
{ "type": "Feature", "properties": { "Name": "Lafayette Heights", "ft": 378, "view": null, "longitude": -122.427590, "latitude": 37.791630 }, "geometry": { "type": "Point", "coordinates": [ -122.427592, 37.791629 ] } },
{ "type": "Feature", "properties": { "Name": "Lincoln Heights", "ft": 380, "view": 17, "longitude": -122.500730, "latitude": 37.783500 }, "geometry": { "type": "Point", "coordinates": [ -122.500734, 37.783503 ] } },
{ "type": "Feature", "properties": { "Name": "Billy Goat Hill", "ft": 400, "view": 18, "longitude": -122.433040, "latitude": 37.741250 }, "geometry": { "type": "Point", "coordinates": [ -122.433035, 37.741247 ] } },
{ "type": "Feature", "properties": { "Name": "Liberty Hill", "ft": 400, "view": null, "longitude": -122.431130, "latitude": 37.755750 }, "geometry": { "type": "Point", "coordinates": [ -122.431126, 37.755753 ] } },
{ "type": "Feature", "properties": { "Name": "Parnassus Heights", "ft": 400, "view": null, "longitude": -122.454860, "latitude": 37.762130 }, "geometry": { "type": "Point", "coordinates": [ -122.454858, 37.762132 ] } },
{ "type": "Feature", "properties": { "Name": "Castro\/Liberty Hill", "ft": 407, "view": null, "longitude": -122.435610, "latitude": 37.754650 }, "geometry": { "type": "Point", "coordinates": [ -122.435612, 37.754651 ] } },
{ "type": "Feature", "properties": { "Name": "Strawberry Hill", "ft": 412, "view": 19, "longitude": -122.475500, "latitude": 37.768540 }, "geometry": { "type": "Point", "coordinates": [ -122.4755, 37.768544 ] } },
{ "type": "Feature", "properties": { "Name": "Lone Mountain", "ft": 448, "view": 20, "longitude": -122.451640, "latitude": 37.778960 }, "geometry": { "type": "Point", "coordinates": [ -122.451639, 37.778958 ] } },
{ "type": "Feature", "properties": { "Name": "Bernal Hill", "ft": 500, "view": 4, "longitude": -122.415240, "latitude": 37.742960 }, "geometry": { "type": "Point", "coordinates": [ -122.415239, 37.742962 ] } },
{ "type": "Feature", "properties": { "Name": "Merced Heights", "ft": 500, "view": 8, "longitude": -122.462670, "latitude": 37.717880 }, "geometry": { "type": "Point", "coordinates": [ -122.462669, 37.717879 ] } },
{ "type": "Feature", "properties": { "Name": "Corona Heights", "ft": 510, "view": 6, "longitude": -122.439360, "latitude": 37.765000 }, "geometry": { "type": "Point", "coordinates": [ -122.43936, 37.765003 ] } },
{ "type": "Feature", "properties": { "Name": "McLaren Ridge", "ft": 515, "view": 11, "longitude": -122.419800, "latitude": 37.719000 }, "geometry": { "type": "Point", "coordinates": [ -122.419796, 37.718998 ] } },
{ "type": "Feature", "properties": { "Name": "Glen Park Hill", "ft": 525, "view": null, "longitude": -122.432160, "latitude": 37.738550 }, "geometry": { "type": "Point", "coordinates": [ -122.432156, 37.738549 ] } },
{ "type": "Feature", "properties": { "Name": "Tank Hill", "ft": 560, "view": 14, "longitude": -122.447690, "latitude": 37.759620 }, "geometry": { "type": "Point", "coordinates": [ -122.447691, 37.759621 ] } },
{ "type": "Feature", "properties": { "Name": "Buena Vista Hill", "ft": 569, "view": 12, "longitude": -122.441570, "latitude": 37.767810 }, "geometry": { "type": "Point", "coordinates": [ -122.441568, 37.767806 ] } },
{ "type": "Feature", "properties": { "Name": "Mt. Olympus", "ft": 570, "view": null, "longitude": -122.445510, "latitude": 37.763290 }, "geometry": { "type": "Point", "coordinates": [ -122.445513, 37.763286 ] } },
{ "type": "Feature", "properties": { "Name": "Monterey Heights", "ft": 575, "view": null, "longitude": -122.460570, "latitude": 37.733870 }, "geometry": { "type": "Point", "coordinates": [ -122.460566, 37.733865 ] } },
{ "type": "Feature", "properties": { "Name": "Grand View Park (Larsen Peak)", "ft": 666, "view": 5, "longitude": -122.471700, "latitude": 37.756210 }, "geometry": { "type": "Point", "coordinates": [ -122.471699, 37.756208 ] } },
{ "type": "Feature", "properties": { "Name": "Gold Mine Hill", "ft": 679, "view": null, "longitude": -122.437260, "latitude": 37.741260 }, "geometry": { "type": "Point", "coordinates": [ -122.437263, 37.741264 ] } },
{ "type": "Feature", "properties": { "Name": "Red Rock Hill", "ft": 689, "view": null, "longitude": -122.441550, "latitude": 37.746320 }, "geometry": { "type": "Point", "coordinates": [ -122.44155, 37.746322 ] } },
{ "type": "Feature", "properties": { "Name": "Mt. Parnassus", "ft": 700, "view": null, "longitude": -122.453440, "latitude": 37.759420 }, "geometry": { "type": "Point", "coordinates": [ -122.453442, 37.759417 ] } },
{ "type": "Feature", "properties": { "Name": "Golden Gate Heights", "ft": 720, "view": null, "longitude": -122.468810, "latitude": 37.749750 }, "geometry": { "type": "Point", "coordinates": [ -122.468805, 37.749747 ] } },
{ "type": "Feature", "properties": { "Name": "Edgehill Mountain", "ft": 731, "view": null, "longitude": -122.459060, "latitude": 37.742620 }, "geometry": { "type": "Point", "coordinates": [ -122.459064, 37.742621 ] } },
{ "type": "Feature", "properties": { "Name": "Forest Hill", "ft": 778, "view": null, "longitude": -122.466960, "latitude": 37.748020 }, "geometry": { "type": "Point", "coordinates": [ -122.46696, 37.748017 ] } },
{ "type": "Feature", "properties": { "Name": "Sherwood Forest Hill", "ft": 825, "view": null, "longitude": -122.456290, "latitude": 37.736660 }, "geometry": { "type": "Point", "coordinates": [ -122.456286, 37.736657 ] } },
{ "type": "Feature", "properties": { "Name": "Clarendon Heights", "ft": 850, "view": null, "longitude": -122.452790, "latitude": 37.755100 }, "geometry": { "type": "Point", "coordinates": [ -122.45279, 37.755096 ] } },
{ "type": "Feature", "properties": { "Name": "Eureka Peak (Twin Peaks North)", "ft": 905, "view": null, "longitude": -122.447420, "latitude": 37.753360 }, "geometry": { "type": "Point", "coordinates": [ -122.447421, 37.753357 ] } },
{ "type": "Feature", "properties": { "Name": "Mt. Sutro", "ft": 909, "view": 9, "longitude": -122.457210, "latitude": 37.758160 }, "geometry": { "type": "Point", "coordinates": [ -122.457209, 37.758162 ] } },
{ "type": "Feature", "properties": { "Name": "Noe Peak (Twin Peaks South)", "ft": 922, "view": 1, "longitude": -122.447710, "latitude": 37.751590 }, "geometry": { "type": "Point", "coordinates": [ -122.447714, 37.751592 ] } },
{ "type": "Feature", "properties": { "Name": "Mt. Davidson", "ft": 925, "view": 2, "longitude": -122.454550, "latitude": 37.738250 }, "geometry": { "type": "Point", "coordinates": [ -122.454552, 37.738252 ] } },
{ "type": "Feature", "properties": { "Name": "Kite Hill", "ft": 300, "view": null, "longitude": -122.441640, "latitude": 37.758160 }, "geometry": { "type": "Point", "coordinates": [ -122.44164, 37.758162 ] } }
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment