Last active
March 22, 2016 22:17
-
-
Save jhnklly/c087666a0b0fde8673f5 to your computer and use it in GitHub Desktop.
Drag & drop csv to map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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]); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content='initial-scale=1,maximum-scale=1,user-scalable=no' /> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script src="http://d3js.org/topojson.v1.min.js" charset="utf-8"></script> | |
<script src="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">×</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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.mapsense-attribution { | |
background-color: rgba(0, 0, 0, 0.5); | |
color: #ccc; | |
position: absolute; | |
bottom: 0; | |
right: 0; | |
padding: 10px; | |
font-size: 11px; | |
font-family: sans-serif; | |
line-height: 5px; | |
text-decoration: none; | |
} | |
.mapsense-attribution a { | |
color: #ccc; | |
text-decoration: none; | |
} | |
.compass .back { | |
fill: #eee; | |
fill-opacity: .8; | |
} | |
.compass .fore { | |
stroke: #999; | |
stroke-width: 1.5px; | |
} | |
.compass rect.back.fore { | |
fill: #999; | |
fill-opacity: .3; | |
stroke: #eee; | |
stroke-width: 1px; | |
shape-rendering: crispEdges; | |
} | |
.compass .direction { | |
fill: none; | |
} | |
.compass .chevron { | |
fill: none; | |
stroke: #999; | |
stroke-width: 5px; | |
} | |
.compass .zoom .chevron { | |
stroke-width: 4px; | |
} | |
.compass .active .chevron, | |
.compass .chevron.active { | |
stroke: #fff; | |
} | |
.compass.active .active .direction { | |
fill: #999; | |
} | |
path { | |
vector-effect: non-scaling-stroke; | |
stroke-linejoin: round; | |
stroke-linecap: round; | |
} | |
/* zoom-dependent styling for roads & admins */ | |
/* general rules */ | |
path._undefined.none { | |
fill: none; | |
} | |
path.landuse.other { | |
fill: none; | |
stroke: none; | |
} | |
.park { | |
opacity: .3; | |
} | |
.water_line { | |
opacity: .5; | |
fill: none; | |
} | |
.runway { | |
opacity: .7; | |
} | |
.rail_major, | |
.rail_minor { | |
stroke-dasharray: 4, 3; | |
} | |
.roads, | |
.admin { | |
fill: none; | |
} | |
.disputed_border, | |
.state_border { | |
stroke-dasharray: 8, 3; | |
} | |
/* admins */ | |
.country_border { | |
stroke-width: 2; | |
} | |
.admin._3, | |
.admin._2, | |
.admin._1 { | |
stroke-width: 1; | |
} | |
.admin._4 { | |
stroke-width: 1.5; | |
} | |
.state_border._2 { | |
stroke-width: .3; | |
} | |
.state_border._3, | |
.state_border._4 { | |
stroke-width: .5; | |
} | |
.state_border._8, | |
.state_border._9, | |
.state_border._10, | |
.state_border._11 { | |
stroke-width: 1.5; | |
opacity: .75; | |
} | |
.state_border._12, | |
.state_border._13, | |
.state_border._14, | |
.state_border._15, | |
.state_border._16, | |
.state_border._17 { | |
stroke-width: 2; | |
stroke-dasharray: 10, 4; | |
opacity: .75; | |
} | |
/* roads */ | |
.ne_10m_roads._3 { | |
stroke-width: 0.5; | |
} | |
.ne_10m_roads._4, | |
.ne_10m_roads._5 { | |
stroke-width: 0.75; | |
} | |
.ne_10m_roads._6 { | |
opacity: 0.75; | |
} | |
.motorway._6 { | |
stroke-width: .75; | |
} | |
.motorway._7, | |
.ne_10m_roads._7 { | |
stroke-width: 1; | |
} | |
.arterial_major._7 { | |
stroke-width: .5; | |
} | |
/* zoom 8 */ | |
.ne_10m_roads._8 { | |
stroke-width: 1.25; | |
opacity: 0.75; | |
} | |
.motorway._8 { | |
stroke-width: 1.25; | |
opacity: 0.75; | |
} | |
.arterial_major._8 { | |
stroke-width: .5; | |
opacity: .75; | |
} | |
.arterial_minor._8 { | |
stroke-width: 0.5; | |
opacity: .5; | |
} | |
/* zoom 9 */ | |
.motorway._9 { | |
stroke-width: 1.5; | |
} | |
.arterial_major._9 { | |
stroke-width: .75; | |
} | |
.arterial_minor._9 { | |
stroke-width: 0.5; | |
opacity: .5; | |
} | |
/* zoom 10 */ | |
.motorway._10 { | |
stroke-width: 1.75; | |
} | |
.arterial_major._10 { | |
stroke-width: 1.25; | |
} | |
.arterial_minor._10 { | |
stroke-width: 1; | |
} | |
.road_med._10 { | |
stroke-width: .75; | |
} | |
/* zoom 11 */ | |
.motorway._11 { | |
stroke-width: 3; | |
} | |
.arterial_major._11, | |
.runway._11 { | |
stroke-width: 1.75; | |
} | |
.arterial_minor._11 { | |
stroke-width: 1.25; | |
} | |
.road_med._11 { | |
stroke-width: 1; | |
} | |
.road_minor._11 { | |
stroke-width: .5; | |
} | |
/* zoom 12 */ | |
.motorway._12 { | |
stroke-width: 3.5; | |
} | |
.arterial_major._12, | |
.runway._12 { | |
stroke-width: 2; | |
} | |
.arterial_minor._12 { | |
stroke-width: 1.5; | |
} | |
.road_med._12 { | |
stroke-width: 1; | |
} | |
.road_minor._12 { | |
stroke-width: .75; | |
} | |
/* zoom 13 */ | |
.motorway._13 { | |
stroke-width: 5; | |
} | |
.arterial_major._13, | |
.runway._13 { | |
stroke-width: 3.5; | |
} | |
.arterial_minor._13 { | |
stroke-width: 2.5; | |
} | |
.road_med._13 { | |
stroke-width: 2; | |
} | |
.road_minor._13 { | |
stroke-width: 1.5; | |
} | |
.path._13, | |
.water_line._13 { | |
stroke-width: 0.3; | |
} | |
/* zoom 14 */ | |
.motorway._14 { | |
stroke-width: 6.5; | |
} | |
.arterial_major._14, | |
.runway._14 { | |
stroke-width: 4.75; | |
} | |
.arterial_minor._14 { | |
stroke-width: 4.25; | |
} | |
.road_med._14 { | |
stroke-width: 3.75; | |
} | |
.road_minor._14 { | |
stroke-width: 2.5; | |
} | |
.rail_minor._14 { | |
stroke-width: 1; | |
} | |
.rail_major._14 { | |
stroke-width: 1.5; | |
} | |
.path._14, | |
.water_line._14 { | |
stroke-width: 0.75; | |
} | |
/* zoom 15 */ | |
.motorway._15 { | |
stroke-width: 8.5; | |
} | |
.arterial_major._15, | |
.runway._15 { | |
stroke-width: 7; | |
} | |
.arterial_minor._15 { | |
stroke-width: 6; | |
} | |
.road_med._15 { | |
stroke-width: 5; | |
} | |
.road_minor._15 { | |
stroke-width: 4; | |
} | |
.rail_minor._15 { | |
stroke-width: 1.5; | |
} | |
.rail_major._15 { | |
stroke-width: 1.5; | |
} | |
.path._15, | |
.water_line._15 { | |
stroke-width: 1; | |
} | |
/* zoom 16 */ | |
.motorway._16 { | |
stroke-width: 12; | |
} | |
.arterial_major._16, | |
.runway._16 { | |
stroke-width: 9; | |
} | |
.arterial_minor._16 { | |
stroke-width: 8; | |
} | |
.road_med._16 { | |
stroke-width: 7; | |
} | |
.road_minor._16 { | |
stroke-width: 6; | |
} | |
.rail_minor._16 { | |
stroke-width: 1.5; | |
} | |
.rail_major._16 { | |
stroke-width: 2; | |
} | |
.path._16, | |
.water_line._16 { | |
stroke-width: 2; | |
} | |
/* zoom 17 */ | |
.motorway._17 { | |
stroke-width: 15; | |
} | |
.arterial_major._17, | |
.runway._17 { | |
stroke-width: 10; | |
} | |
.arterial_minor._17 { | |
stroke-width: 9; | |
} | |
.road_med._17 { | |
stroke-width: 8; | |
} | |
.road_minor._17 { | |
stroke-width: 8; | |
} | |
.rail_minor._17 { | |
stroke-width: 3; | |
stroke-dasharray: 7, 6; | |
} | |
.rail_major._17 { | |
stroke-width: 4; | |
stroke-dasharray: 7, 6; | |
} | |
.path._17, | |
.water_line._17 { | |
stroke-width: 5; | |
} | |
/* zoom 18 */ | |
.motorway._18 { | |
stroke-width: 16; | |
} | |
.arterial_major._18, | |
.runway._18 { | |
stroke-width: 10; | |
} | |
.arterial_minor._18 { | |
stroke-width: 10; | |
} | |
.road_med._18 { | |
stroke-width: 9; | |
} | |
.road_minor._18 { | |
stroke-width: 8; | |
} | |
.rail_minor._18 { | |
stroke-width: 3; | |
stroke-dasharray: 7, 6; | |
} | |
.rail_major._18 { | |
stroke-width: 4; | |
stroke-dasharray: 7, 6; | |
} | |
.path._18, | |
.water_line._18 { | |
stroke-width: 5; | |
} | |
.mapsense-blackprint.labels { | |
font-size: 18; | |
fill: #999; | |
font-weight: 200; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-blackprint.tile-background { | |
fill: #141414; | |
} | |
.mapsense-blackprint.land { | |
fill: #000000; | |
stroke: #bdbcbc; | |
stroke-width: 1; | |
} | |
.mapsense-blackprint.agriculture { | |
display: none; | |
} | |
.mapsense-blackprint.water_polygon { | |
fill: #141414; | |
} | |
.mapsense-blackprint.water_line { | |
stroke: #323232; | |
stroke-width: 1; | |
opacity: .3; | |
fill: none; | |
} | |
.mapsense-blackprint.park { | |
fill: rgba(15, 15, 15, 0.3); | |
stroke: none; | |
} | |
.mapsense-blackprint.building { | |
fill: none; | |
stroke: #505050; | |
stroke-width: .52; | |
} | |
.mapsense-blackprint.school { | |
fill: #0f0f0f; | |
stroke: none; | |
} | |
.mapsense-blackprint.other { | |
stroke: #323232; | |
} | |
.mapsense-blackprint.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-blackprint.roads { | |
stroke: #222; | |
} | |
.mapsense-blackprint.motorway, | |
.mapsense-blackprint.ne_10m_roads { | |
stroke: #333; | |
} | |
.mapsense-blackprint.country_border, | |
.mapsense-blackprint.disputed_border { | |
stroke: grey; | |
stroke-dasharray: 3 3; | |
stroke-width: 1; | |
fill: none; | |
} | |
.mapsense-blackprint.state_border { | |
stroke: grey; | |
stroke-dasharray: 3 3; | |
fill: none; | |
} | |
.mapsense-dark.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-dark.tile-background { | |
fill: #3a4250; | |
} | |
.mapsense-dark.land { | |
fill: #161619; | |
} | |
.mapsense-dark.agriculture { | |
display: none; | |
} | |
.mapsense-dark.country_border, | |
.mapsense-dark.disputed_border { | |
stroke: #777; | |
} | |
.mapsense-dark.state_border { | |
stroke: #777; | |
} | |
.mapsense-dark.water_polygon { | |
fill: #3a4250; | |
stroke: none; | |
} | |
.mapsense-dark.water_line { | |
stroke: #516283; | |
stroke-width: 1; | |
fill: none; | |
} | |
.mapsense-dark.park { | |
fill: #268061; | |
} | |
.mapsense-dark.building { | |
fill: #222; | |
stroke: black; | |
} | |
.mapsense-dark.school { | |
fill: #35161e; | |
} | |
.mapsense-dark.other { | |
fill: rgba(76, 69, 67, 0.15); | |
stroke: black; | |
} | |
.mapsense-dark.urban { | |
fill: #000000; | |
stroke: none; | |
} | |
.mapsense-dark.ne_10m_roads { | |
stroke: #7c5c2c; | |
} | |
.mapsense-dark.motorway { | |
stroke: #7c5c2c; | |
} | |
.mapsense-dark.arterial_major { | |
stroke: #4a472a; | |
} | |
.mapsense-dark.arterial_minor { | |
stroke: #3c3c3c; | |
} | |
.mapsense-dark.road_med { | |
stroke: #3c3c3c; | |
} | |
.mapsense-dark.road_minor { | |
stroke: #323232; | |
} | |
.mapsense-dark.rail_major { | |
stroke: #323228; | |
fill: none; | |
} | |
.mapsense-dark.rail_minor { | |
stroke: #323228; | |
fill: none; | |
} | |
.mapsense-dark.runway { | |
stroke: #333; | |
} | |
.mapsense-dark.path { | |
stroke: #323228; | |
} | |
.mapsense-grayscale.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-grayscale.tile-background { | |
fill: #aaa9af; | |
} | |
.mapsense-grayscale.land { | |
fill: #e8e5e5; | |
stroke: none; | |
} | |
.mapsense-grayscale.water_polygon { | |
fill: #aaa9af; | |
} | |
.mapsense-grayscale.water_line { | |
stroke: gray; | |
stroke-width: 1; | |
} | |
.mapsense-grayscale.park { | |
fill: #cad4ca; | |
} | |
.mapsense-grayscale.building { | |
fill: #f0f0f0; | |
} | |
.mapsense-grayscale.school { | |
fill: #dcdcdc; | |
stroke: none; | |
} | |
.mapsense-grayscale.other { | |
fill: #cbcbcb; | |
stroke: none; | |
} | |
.mapsense-grayscale.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-grayscale.ne_10m_roads { | |
stroke: #ccc; | |
} | |
.mapsense-grayscale.motorway { | |
stroke: #CCC; | |
} | |
.mapsense-grayscale.arterial_major { | |
stroke: #d2d2d2; | |
} | |
.mapsense-grayscale.arterial_minor { | |
stroke: #d7d7d7; | |
} | |
.mapsense-grayscale.road_med { | |
stroke: #DDD; | |
} | |
.mapsense-grayscale.road_minor { | |
stroke: #DDD; | |
} | |
.mapsense-grayscale.rail_major { | |
stroke: lightgray; | |
} | |
.mapsense-grayscale.rail_minor { | |
stroke: lightgray; | |
} | |
.mapsense-grayscale.runway { | |
stroke: #f2efef; | |
} | |
.mapsense-grayscale.path { | |
stroke: #DDD; | |
} | |
.mapsense-grayscale.country_border, | |
.mapsense-grayscale.disputed_border { | |
stroke: #BBB; | |
} | |
.mapsense-grayscale.state_border { | |
stroke: #bbb; | |
} | |
.mapsense-light.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-light.tile-background { | |
fill: #ffffff; | |
} | |
.mapsense-light.land { | |
fill: #ddddda; | |
stroke: none; | |
} | |
.mapsense-light.water_polygon { | |
fill: #ffffff; | |
stroke: none; | |
} | |
.mapsense-light.country_border, | |
.mapsense-light.disputed_border { | |
stroke: #aaa; | |
} | |
.mapsense-light.state_border { | |
stroke: #aaa; | |
} | |
.mapsense-light.water_line { | |
stroke: #c8d0df; | |
} | |
.mapsense-light.park { | |
fill: #caceb9; | |
stroke: none; | |
} | |
.mapsense-light.building { | |
fill: #d7d6d1; | |
} | |
.mapsense-light.school { | |
fill: #eadddb; | |
stroke: none; | |
} | |
.mapsense-light.other { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-light.urban { | |
fill: rgba(177, 135, 74, 0.07); | |
stroke: none; | |
} | |
.mapsense-light.ne_10m_roads { | |
stroke: #ededed; | |
} | |
.mapsense-light.motorway { | |
stroke: #f6f4f3; | |
} | |
.mapsense-light.arterial_major { | |
stroke: #f4f4ef; | |
} | |
.mapsense-light.arterial_minor { | |
stroke: #f1eded; | |
} | |
.mapsense-light.road_med { | |
stroke: #f6f3f3; | |
} | |
.mapsense-light.road_minor { | |
stroke: #faf8f8; | |
} | |
.mapsense-light.rail_major { | |
stroke: #cccccc; | |
} | |
.mapsense-light.rail_minor { | |
stroke: #cccccc; | |
} | |
.mapsense-light.runway { | |
stroke: #ccc; | |
} | |
.mapsense-light.path { | |
stroke: #ccc; | |
} | |
.mapsense-parchment.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
/*stroke: grey;*/ | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-parchment.tile-background { | |
fill: #b8bdc1; | |
} | |
.mapsense-parchment.land { | |
fill: #e3d6c5; | |
stroke: #976f60; | |
stroke-width: 1; | |
} | |
.mapsense-parchment.water_polygon { | |
fill: #b8bdc1; | |
stroke: #976f60; | |
stroke-width: .5; | |
} | |
.mapsense-parchment.water_line { | |
stroke: #b8bdc1; | |
} | |
.mapsense-parchment.park { | |
fill: #cbbca6; | |
} | |
.mapsense-parchment.building { | |
fill: #d4c9ba; | |
} | |
.mapsense-parchment.school { | |
fill: rgba(90, 46, 5, 0.12); | |
stroke: none; | |
} | |
.mapsense-parchment.other { | |
fill: #e3d6c5; | |
stroke: none; | |
} | |
.mapsense-parchment.urban { | |
fill: rgba(255, 255, 255, 0.3); | |
stroke: none; | |
} | |
.mapsense-parchment.ne_10m_roads { | |
stroke: #aea295; | |
} | |
.mapsense-parchment.motorway { | |
stroke: #aea295; | |
} | |
.mapsense-parchment.arterial_major { | |
stroke: #d5c5b7; | |
} | |
.mapsense-parchment.arterial_minor { | |
stroke: #cab7aa; | |
} | |
.mapsense-parchment.road_med { | |
stroke: #d8c8bb; | |
} | |
.mapsense-parchment.road_minor { | |
stroke: #d8c8bb; | |
} | |
.mapsense-parchment.rail_major { | |
stroke: #bda897; | |
} | |
.mapsense-parchment.rail_minor { | |
stroke: #bda897; | |
} | |
.mapsense-parchment.runway { | |
stroke: #ede5d5; | |
} | |
.mapsense-parchment.path { | |
stroke: #ede5d5; | |
} | |
.mapsense-parchment.country_border, | |
.mapsense-parchment.disputed_border { | |
stroke: #976f60; | |
} | |
.mapsense-parchment.state_border { | |
stroke: #976f60; | |
} | |
.mapsense-sketch.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-sketch.tile-background { | |
fill: #f0f0f0; | |
} | |
.mapsense-sketch.land { | |
fill: #ffffff; | |
stroke: #bdbcbc; | |
stroke-width: 1; | |
} | |
.mapsense-sketch.water_polygon { | |
fill: #f0f0f0; | |
stroke: #bdbcbc; | |
stroke-width: .5; | |
} | |
.mapsense-sketch.water_line { | |
stroke: #cccaca; | |
} | |
.mapsense-sketch.park { | |
fill: none; | |
stroke-width: .55; | |
stroke: #dad8d8; | |
} | |
.mapsense-sketch.building { | |
fill: none; | |
stroke: #d4d4d4; | |
stroke-width: .52; | |
} | |
.mapsense-sketch.school { | |
fill: none; | |
stroke: #dadada; | |
} | |
.mapsense-sketch.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-sketch.ne_10m_roads { | |
stroke: #eee; | |
} | |
.mapsense-sketch.motorway { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.arterial_major { | |
stroke: #eee; | |
} | |
.mapsense-sketch.arterial_minor { | |
stroke: #eee; | |
} | |
.mapsense-sketch.road_med { | |
stroke: #eee; | |
} | |
.mapsense-sketch.road_minor { | |
stroke: #f5f5f5; | |
} | |
.mapsense-sketch.rail_major { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.rail_minor { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.runway { | |
stroke: #ddd; | |
} | |
.mapsense-sketch.path { | |
stroke: #f5f5f5; | |
} | |
.mapsense-sketch.country_border, | |
.mapsense-sketch.disputed_border { | |
stroke: #ccc; | |
} | |
.mapsense-sketch.state_border { | |
stroke: #ccc; | |
} | |
.mapsense-tron.labels { | |
font-size: 14; | |
fill: #888; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-tron.tile-background { | |
fill: #000000; | |
} | |
.mapsense-tron.land { | |
fill: #172c3a; | |
stroke: none; | |
} | |
.mapsense-tron.water_polygon { | |
stroke: none; | |
fill: black; | |
} | |
.mapsense-tron.country_border, | |
.mapsense-tron.disputed_border { | |
stroke: #666; | |
} | |
.mapsense-tron.state_border { | |
stroke: #666; | |
} | |
.mapsense-tron.water_line { | |
stroke: #111119; | |
} | |
.mapsense-tron.park { | |
fill: #060a10; | |
} | |
.mapsense-tron.building { | |
fill: #07212a; | |
stroke: none; | |
} | |
.mapsense-tron.school { | |
fill: #162532; | |
stroke: none; | |
} | |
.mapsense-tron.other { | |
fill: #11242d; | |
stroke: none; | |
} | |
.mapsense-tron.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-tron.ne_10m_roads { | |
stroke: #36606f; | |
} | |
.mapsense-tron.motorway { | |
stroke: #36606f; | |
} | |
.mapsense-tron.arterial_major { | |
stroke: #295563; | |
} | |
.mapsense-tron.arterial_minor { | |
stroke: #295563; | |
} | |
.mapsense-tron.road_med { | |
stroke: #2b3e49; | |
} | |
.mapsense-tron.road_minor { | |
stroke: #1b333a; | |
} | |
.mapsense-tron.rail_major { | |
stroke: #1b333a; | |
} | |
.mapsense-tron.rail_minor { | |
stroke: #1b333a; | |
} | |
.mapsense-tron.runway { | |
stroke: #1e414c; | |
} | |
.mapsense-tron.path { | |
stroke: #1b333a; | |
} | |
.mapsense-vintage.labels { | |
font-size: 16; | |
fill: #4c83b2; | |
font-weight: 400; | |
text-transform: uppercase; | |
stroke-width: .3; | |
stroke: grey; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-vintage.tile-background { | |
fill: #87c8ca; | |
} | |
.mapsense-vintage.land { | |
fill: #fffaf2; | |
} | |
.mapsense-vintage.water_polygon { | |
fill: #87c8ca; | |
} | |
.mapsense-vintage.country_border, | |
.mapsense-vintage.disputed_border { | |
stroke: #aaa; | |
} | |
.mapsense-vintage.state_border { | |
stroke: #aaa; | |
} | |
.mapsense-vintage.water_line { | |
stroke: #b5e2e4; | |
} | |
.mapsense-vintage.park { | |
fill: #c6f3bd; | |
stroke: none; | |
} | |
.mapsense-vintage.building { | |
fill: #f9ece2; | |
stroke: none; | |
} | |
.mapsense-vintage.school { | |
fill: #f0eced; | |
stroke: none; | |
} | |
.mapsense-vintage.urban { | |
fill: rgba(243, 210, 191, 0.19); | |
stroke: none; | |
} | |
.mapsense-vintage.ne_10m_roads { | |
stroke: #ffb67e; | |
} | |
.mapsense-vintage.motorway { | |
stroke: #f6bd86; | |
} | |
.mapsense-vintage.arterial_major { | |
stroke: #c2d7df; | |
} | |
.mapsense-vintage.arterial_minor { | |
stroke: #cfdddb; | |
} | |
.mapsense-vintage.road_med { | |
stroke: #dae9ea; | |
} | |
.mapsense-vintage.road_minor { | |
stroke: #ededed; | |
} | |
.mapsense-vintage.rail_major { | |
stroke: #c7c4c4; | |
} | |
.mapsense-vintage.rail_minor { | |
stroke: #c7c4c4; | |
} | |
.mapsense-vintage.runway { | |
stroke: #e1dede; | |
} | |
.mapsense-vintage.path { | |
stroke: #e1dede; | |
} | |
.mapsense-simple.labels { | |
font-size: 14; | |
fill: #777; | |
font-weight: 600; | |
text-transform: uppercase; | |
stroke-width: .3; | |
font-stretch: expanded; | |
letter-spacing: 1.5; | |
font-family: "Josefin Sans"; | |
} | |
.mapsense-simple.tile-background { | |
fill: #CBE6F3; | |
} | |
.mapsense-simple.land { | |
fill: #ffffff; | |
stroke: #a2d3f2; | |
stroke-width: 1; | |
} | |
.mapsense-simple.water_polygon { | |
fill: #CBE6F3; | |
stroke: #b6d3e0; | |
stroke-width: 0.5px; | |
} | |
.mapsense-simple.water_line { | |
stroke: #a2d3f2; | |
} | |
.mapsense-simple.park { | |
fill: none; | |
stroke-width: 0; | |
stroke: #dad8d8; | |
} | |
.mapsense-simple.landuse { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.building { | |
fill: none; | |
stroke: #d4d4d4; | |
stroke-width: .52; | |
} | |
.mapsense-simple.school { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.other { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.urban { | |
fill: none; | |
stroke: none; | |
} | |
.mapsense-simple.ne_10m_roads { | |
stroke: #eee; | |
} | |
.mapsense-simple.motorway { | |
stroke: #ddd; | |
} | |
.mapsense-simple.arterial_major { | |
stroke: #eee; | |
} | |
.mapsense-simple.arterial_minor { | |
stroke: #eee; | |
} | |
.mapsense-simple.road_med { | |
stroke: #eee; | |
} | |
.mapsense-simple.road_minor { | |
stroke: #f5f5f5; | |
} | |
.mapsense-simple.rail_major { | |
stroke: #ddd; | |
} | |
.mapsense-simple.rail_minor { | |
stroke: #ddd; | |
} | |
.mapsense-simple.runway { | |
stroke: #ddd; | |
} | |
.mapsense-simple.path { | |
stroke: #f5f5f5; | |
} | |
.mapsense-simple.country_border, | |
.mapsense-simple.disputed_border { | |
stroke: #ccc; | |
} | |
.mapsense-simple.state_border { | |
stroke: #ccc; | |
} | |
.mapsense-simple.ne_10m_roads._3 { | |
stroke-width: 0.5; | |
stroke: none; | |
} | |
.mapsense-simple.ne_10m_roads._4, | |
.mapsense-simple.ne_10m_roads._5 { | |
stroke-width: 0.75; | |
stroke: none; | |
} | |
.mapsense-simple.ne_10m_roads._6 { | |
opacity: 0.75; | |
stroke: none; | |
} | |
svg.mapsense-map { | |
width: 100%; | |
height: 100%; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function(){ | |
var mapsense = {version: "1.0.0"}, | |
ms = mapsense; | |
var zero = {x: 0, y: 0}; | |
ms.ns = { | |
svg: "http://www.w3.org/2000/svg", | |
xlink: "http://www.w3.org/1999/xlink" | |
}; | |
function ns(name) { | |
var i = name.indexOf(":"); | |
return i < 0 ? name : { | |
space: ms.ns[name.substring(0, i)], | |
local: name.substring(i + 1) | |
}; | |
} | |
ms.id = (function() { | |
var id = 0; | |
return function() { | |
return ++id; | |
}; | |
})(); | |
ms.svg = function(type) { | |
return document.createElementNS(ms.ns.svg, type); | |
}; | |
ms.transform = function(a, b, c, d, e, f) { | |
var transform = {}, | |
zoomDelta, | |
zoomFraction, | |
k; | |
if (!arguments.length) { | |
a = 1; c = 0; e = 0; | |
b = 0; d = 1; f = 0; | |
} | |
transform.zoomFraction = function(x) { | |
if (!arguments.length) return zoomFraction; | |
zoomFraction = x; | |
zoomDelta = Math.floor(zoomFraction + Math.log(Math.sqrt(a * a + b * b + c * c + d * d)) / Math.LN2); | |
k = Math.pow(2, -zoomDelta); | |
return transform; | |
}; | |
transform.apply = function(x) { | |
var k0 = Math.pow(2, -x.zoom), | |
k1 = Math.pow(2, x.zoom - zoomDelta); | |
return { | |
column: (a * x.column * k0 + c * x.row * k0 + e) * k1, | |
row: (b * x.column * k0 + d * x.row * k0 + f) * k1, | |
zoom: x.zoom - zoomDelta | |
}; | |
}; | |
transform.unapply = function(x) { | |
var k0 = Math.pow(2, -x.zoom), | |
k1 = Math.pow(2, x.zoom + zoomDelta); | |
return { | |
column: (x.column * k0 * d - x.row * k0 * c - e * d + f * c) / (a * d - b * c) * k1, | |
row: (x.column * k0 * b - x.row * k0 * a - e * b + f * a) / (c * b - d * a) * k1, | |
zoom: x.zoom + zoomDelta | |
}; | |
}; | |
transform.toString = function() { | |
return "matrix(" + [a * k, b * k, c * k, d * k].join(" ") + " 0 0)"; | |
}; | |
return transform.zoomFraction(0); | |
}; | |
ms.cache = function(load, unload) { | |
var cache = {}, | |
locks = {}, | |
map = {}, | |
head = null, | |
tail = null, | |
size = 64, | |
n = 0; | |
function remove(tile) { | |
n--; | |
if (unload) unload(tile); | |
delete map[tile.key]; | |
if (tile.next) tile.next.prev = tile.prev; | |
else if (tail = tile.prev) tail.next = null; | |
if (tile.prev) tile.prev.next = tile.next; | |
else if (head = tile.next) head.prev = null; | |
} | |
function flush() { | |
for (var tile = tail; n > size; tile = tile.prev) { | |
if (!tile) break; | |
if (tile.lock) continue; | |
remove(tile); | |
} | |
} | |
cache.peek = function(c) { | |
return map[[c.zoom, c.column, c.row].join("/")]; | |
}; | |
cache.load = function(c, projection) { | |
var key = [c.zoom, c.column, c.row].join("/"), | |
tile = map[key]; | |
if (tile) { | |
if (tile.prev) { | |
tile.prev.next = tile.next; | |
if (tile.next) tile.next.prev = tile.prev; | |
else tail = tile.prev; | |
tile.prev = null; | |
tile.next = head; | |
head.prev = tile; | |
head = tile; | |
} | |
tile.lock = 1; | |
locks[key] = tile; | |
return tile; | |
} | |
tile = { | |
key: key, | |
column: c.column, | |
row: c.row, | |
zoom: c.zoom, | |
next: head, | |
prev: null, | |
lock: 1 | |
}; | |
load.call(null, tile, projection); | |
locks[key] = map[key] = tile; | |
if (head) head.prev = tile; | |
else tail = tile; | |
head = tile; | |
n++; | |
return tile; | |
}; | |
cache.unload = function(key) { | |
if (!(key in locks)) return false; | |
var tile = locks[key]; | |
tile.lock = 0; | |
delete locks[key]; | |
if (tile.request && tile.request.abort(false)) remove(tile); | |
return tile; | |
}; | |
cache.locks = function() { | |
return locks; | |
}; | |
cache.size = function(x) { | |
if (!arguments.length) return size; | |
size = x; | |
flush(); | |
return cache; | |
}; | |
cache.flush = function() { | |
flush(); | |
return cache; | |
}; | |
cache.clear = function() { | |
for (var key in map) { | |
var tile = map[key]; | |
if (tile.request) tile.request.abort(false); | |
if (unload) unload(map[key]); | |
if (tile.lock) { | |
tile.lock = 0; | |
tile.element.parentNode.removeChild(tile.element); | |
} | |
} | |
locks = {}; | |
map = {}; | |
head = tail = null; | |
n = 0; | |
return cache; | |
}; | |
return cache; | |
}; | |
ms.url = function(template) { | |
var hosts = [], | |
repeat = true; | |
function format(c) { | |
var max = c.zoom < 0 ? 1 : 1 << c.zoom, | |
column = c.column; | |
if (repeat) { | |
column = c.column % max; | |
if (column < 0) column += max; | |
} else if ((column < 0) || (column >= max)) { | |
return null; | |
} | |
return (typeof template === "function" ? template(c) : template).replace(/{(.)}/g, function(s, v) { | |
switch (v) { | |
case "S": return hosts[(Math.abs(c.zoom) + c.row + column) % hosts.length]; | |
case "Z": return c.zoom; | |
case "X": return column; | |
case "Y": return c.row; | |
case "B": { | |
var nw = ms.map.coordinateLocation({row: c.row, column: column, zoom: c.zoom}), | |
se = ms.map.coordinateLocation({row: c.row + 1, column: column + 1, zoom: c.zoom}), | |
pn = Math.ceil(Math.log(c.zoom) / Math.LN2); | |
return se.lat.toFixed(pn) + | |
"," + nw.lon.toFixed(pn) + | |
"," + nw.lat.toFixed(pn) + | |
"," + se.lon.toFixed(pn); | |
} | |
} | |
return v; | |
}); | |
} | |
format.template = function(x) { | |
if (!arguments.length) return template; | |
template = x; | |
return format; | |
}; | |
format.hosts = function(x) { | |
if (!arguments.length) return hosts; | |
hosts = x; | |
return format; | |
}; | |
format.repeat = function(x) { | |
if (!arguments.length) return repeat; | |
repeat = x; | |
return format; | |
}; | |
return format; | |
}; | |
ms.dispatch = function(that) { | |
var types = {}; | |
that.on = function(type, handler) { | |
var listeners = types[type] || (types[type] = []); | |
for (var i = 0; i < listeners.length; i++) { | |
if (listeners[i].handler == handler) return that; // already registered | |
} | |
listeners.push({handler: handler, on: true}); | |
return that; | |
}; | |
that.off = function(type, handler) { | |
var listeners = types[type]; | |
if (listeners) for (var i = 0; i < listeners.length; i++) { | |
var l = listeners[i]; | |
if (l.handler == handler) { | |
l.on = false; | |
listeners.splice(i, 1); | |
break; | |
} | |
} | |
return that; | |
}; | |
return function(event) { | |
var listeners = types[event.type]; | |
if (!listeners) return; | |
listeners = listeners.slice(); // defensive copy | |
for (var i = 0; i < listeners.length; i++) { | |
var l = listeners[i]; | |
if (l.on) l.handler.call(that, event); | |
} | |
}; | |
}; | |
ms.queue = (function() { | |
var queued = [], active = 0, size = 6; | |
function process() { | |
if ((active >= size) || !queued.length) return; | |
active++; | |
queued.pop()(); | |
} | |
function dequeue(send) { | |
for (var i = 0; i < queued.length; i++) { | |
if (queued[i] == send) { | |
queued.splice(i, 1); | |
return true; | |
} | |
} | |
return false; | |
} | |
function merge(dest, src) { | |
for (var property in src) { | |
dest[property] = src[property]; | |
} | |
return dest; | |
} | |
function request(url, callback, options) { | |
var req; | |
function send() { | |
req = new XMLHttpRequest(); | |
req.open("GET", url, true); | |
if (options) { | |
if (options.mimeType && req.overrideMimeType) | |
req.overrideMimeType(options.mimeType); | |
if (options.responseType) | |
req.responseType = options.responseType; | |
if (options.xhrFields) { | |
for (var f in options.xhrFields) { | |
req[f] = options.xhrFields[f]; | |
} | |
} | |
} | |
req.onreadystatechange = function(e) { | |
if (req.readyState == 4) { | |
active--; | |
if (req.status < 300) callback(req); | |
process(); | |
} | |
}; | |
req.send(null); | |
} | |
function abort(hard) { | |
if (dequeue(send)) return true; | |
if (hard && req) { req.abort(); return true; } | |
return false; | |
} | |
queued.push(send); | |
process(); | |
return {abort: abort}; | |
} | |
function text(url, callback, mimeType) { | |
return request(url, function(req) { | |
if (req.responseText) callback(req.responseText); | |
}, { mimeType: mimeType }); | |
} | |
/* | |
* We the override MIME type here so that you can load local files; some | |
* browsers don't assign a proper MIME type for local files. | |
*/ | |
function json(url, callback, options) { | |
return request(url, function(req) { | |
if (req.responseText) callback(JSON.parse(req.responseText)); | |
}, merge({ mimeType: "application/json" }, options)); | |
} | |
function xml(url, callback, options) { | |
return request(url, function(req) { | |
if (req.responseXML) callback(req.responseXML); | |
}, merge({ mimeType: "application/xml" }, options)); | |
} | |
function octetStream(url, callback, options) { | |
var defaultOptions = { | |
mimeType: "application/octet-stream", | |
responseType: "arraybuffer" | |
}; | |
return request(url, function(req) { | |
if (req.response) callback(req.response); | |
}, merge(defaultOptions, options)); | |
} | |
function image(imageElement, src, callback) { | |
var img; | |
function send() { | |
img = document.createElement("img"); | |
img.onerror = function() { | |
active--; | |
process(); | |
}; | |
img.onload = function() { | |
active--; | |
callback(img); | |
process(); | |
}; | |
img.src = src; | |
imageElement.setAttributeNS(ms.ns.xlink, "href", src); | |
} | |
function abort(hard) { | |
if (dequeue(send)) return true; | |
if (hard && img) { img.src = "about:"; return true; } // cancels request | |
return false; | |
} | |
queued.push(send); | |
process(); | |
return {abort: abort}; | |
} | |
return { | |
text: text, | |
xml: xml, | |
json: json, | |
octetStream: octetStream, | |
image: image | |
}; | |
})(); | |
ms.map = function(mapContainer) { | |
var map = {}, | |
container, | |
size, | |
sizeActual = zero, | |
sizeRadius = zero, // sizeActual / 2 | |
tileSize = {x: 512, y: 512}, | |
center = {lat: 37.76487, lon: -122.41948}, | |
zoom = 12, | |
zoomFraction = 0, | |
zoomFactor = 1, // Math.pow(2, zoomFraction) | |
zoomRange = [1, 18], | |
angle = 0, | |
angleCos = 1, // Math.cos(angle) | |
angleSin = 0, // Math.sin(angle) | |
angleCosi = 1, // Math.cos(-angle) | |
angleSini = 0, // Math.sin(-angle) | |
ymin = -180, // lat2y(centerRange[0].lat) | |
ymax = 180; // lat2y(centerRange[1].lat) | |
var centerRange = [ | |
{lat: y2lat(ymin), lon: -Infinity}, | |
{lat: y2lat(ymax), lon: Infinity} | |
]; | |
var interact = ms.interact(); | |
if (typeof mapContainer === "string") | |
container = document.querySelector(mapContainer); | |
else | |
container = mapContainer; | |
if (!container) | |
throw new Error("Invalid map container."); | |
map.interact = function(x) { | |
if (!arguments.length) return interact; | |
interact.map(x ? map : null); | |
}; | |
map.locationCoordinate = function(l) { | |
var c = ms.map.locationCoordinate(l), | |
k = Math.pow(2, zoom); | |
c.column *= k; | |
c.row *= k; | |
c.zoom += zoom; | |
return c; | |
}; | |
map.coordinateLocation = ms.map.coordinateLocation; | |
map.coordinatePoint = function(tileCenter, c) { | |
var kc = Math.pow(2, zoom - c.zoom), | |
kt = Math.pow(2, zoom - tileCenter.zoom), | |
dx = (c.column * kc - tileCenter.column * kt) * tileSize.x * zoomFactor, | |
dy = (c.row * kc - tileCenter.row * kt) * tileSize.y * zoomFactor; | |
return { | |
x: sizeRadius.x + angleCos * dx - angleSin * dy, | |
y: sizeRadius.y + angleSin * dx + angleCos * dy | |
}; | |
}; | |
map.pointCoordinate = function(tileCenter, p) { | |
var kt = Math.pow(2, zoom - tileCenter.zoom), | |
dx = (p.x - sizeRadius.x) / zoomFactor, | |
dy = (p.y - sizeRadius.y) / zoomFactor; | |
return { | |
column: tileCenter.column * kt + (angleCosi * dx - angleSini * dy) / tileSize.x, | |
row: tileCenter.row * kt + (angleSini * dx + angleCosi * dy) / tileSize.y, | |
zoom: zoom | |
}; | |
}; | |
map.locationPoint = function(l) { | |
var k = Math.pow(2, zoom + zoomFraction - 3) / 45, | |
dx = (l.lon - center.lon) * k * tileSize.x, | |
dy = (lat2y(center.lat) - lat2y(l.lat)) * k * tileSize.y; | |
return { | |
x: sizeRadius.x + angleCos * dx - angleSin * dy, | |
y: sizeRadius.y + angleSin * dx + angleCos * dy | |
}; | |
}; | |
map.pointLocation = function(p) { | |
var k = 45 / Math.pow(2, zoom + zoomFraction - 3), | |
dx = (p.x - sizeRadius.x) * k, | |
dy = (p.y - sizeRadius.y) * k; | |
return { | |
lon: center.lon + (angleCosi * dx - angleSini * dy) / tileSize.x, | |
lat: y2lat(lat2y(center.lat) - (angleSini * dx + angleCosi * dy) / tileSize.y) | |
}; | |
}; | |
function rezoom() { | |
if (zoomRange) { | |
if (zoom < zoomRange[0]) zoom = zoomRange[0]; | |
else if (zoom > zoomRange[1]) zoom = zoomRange[1]; | |
} | |
zoomFraction = zoom - (zoom = Math.round(zoom)); | |
zoomFactor = Math.pow(2, zoomFraction); | |
} | |
function recenter() { | |
if (!centerRange) return; | |
var k = 45 / Math.pow(2, zoom + zoomFraction - 3); | |
// constrain latitude | |
var y = Math.max(Math.abs(angleSin * sizeRadius.x + angleCos * sizeRadius.y), | |
Math.abs(angleSini * sizeRadius.x + angleCosi * sizeRadius.y)), | |
lat0 = y2lat(ymin - y * k / tileSize.y), | |
lat1 = y2lat(ymax + y * k / tileSize.y); | |
center.lat = Math.max(lat0, Math.min(lat1, center.lat)); | |
// constrain longitude | |
var x = Math.max(Math.abs(angleSin * sizeRadius.y + angleCos * sizeRadius.x), | |
Math.abs(angleSini * sizeRadius.y + angleCosi * sizeRadius.x)), | |
lon0 = centerRange[0].lon - x * k / tileSize.x, | |
lon1 = centerRange[1].lon + x * k / tileSize.x; | |
center.lon = Math.max(lon0, Math.min(lon1, center.lon)); | |
} | |
// a place to capture mouse events if no tiles exist | |
var rect = ms.svg("rect"); | |
rect.setAttribute("visibility", "hidden"); | |
rect.setAttribute("pointer-events", "all"); | |
var svgContainer = ms.svg("svg"); | |
svgContainer.setAttribute("class", "mapsense-map"); | |
svgContainer.appendChild(rect); | |
var relativeContainer = document.createElement("div"); | |
relativeContainer.style.setProperty("position", "relative"); | |
relativeContainer.style.setProperty("width", "100%"); | |
relativeContainer.style.setProperty("height", "100%"); | |
relativeContainer.appendChild(svgContainer); | |
container.appendChild(relativeContainer); | |
map.container = function() { | |
return container; | |
}; | |
map.relativeContainer = function() { | |
return relativeContainer; | |
}; | |
map.svgContainer = function() { | |
return svgContainer; | |
}; | |
map.focusableParent = function() { | |
for (var p = container; p; p = p.parentNode) { | |
if (p.tabIndex >= 0) return p; | |
} | |
return window; | |
}; | |
map.mouse = function(e) { | |
var point = (svgContainer.ownerSVGElement || svgContainer).createSVGPoint(); | |
if ((bug44083 < 0) && (window.scrollX || window.scrollY)) { | |
var svg = document.body.appendChild(ms.svg("svg")); | |
svg.style.position = "absolute"; | |
svg.style.top = svg.style.left = "0px"; | |
var ctm = svg.getScreenCTM(); | |
bug44083 = !(ctm.f || ctm.e); | |
document.body.removeChild(svg); | |
} | |
if (bug44083) { | |
point.x = e.pageX; | |
point.y = e.pageY; | |
} else { | |
point.x = e.clientX; | |
point.y = e.clientY; | |
} | |
return point.matrixTransform(svgContainer.getScreenCTM().inverse()); | |
}; | |
map.size = function(x) { | |
if (!arguments.length) return sizeActual; | |
size = x; | |
return map.resize(); // size tiles | |
}; | |
map.resize = function() { | |
if (!size) { | |
rect.setAttribute("width", "100%"); | |
rect.setAttribute("height", "100%"); | |
var b = rect.getBBox(); | |
sizeActual = {x: b.width, y: b.height}; | |
resizer.add(map); | |
} else { | |
sizeActual = size; | |
resizer.remove(map); | |
} | |
rect.setAttribute("width", sizeActual.x); | |
rect.setAttribute("height", sizeActual.y); | |
sizeRadius = {x: sizeActual.x / 2, y: sizeActual.y / 2}; | |
recenter(); | |
map.dispatch({type: "resize"}); | |
return map; | |
}; | |
map.tileSize = function(x) { | |
if (!arguments.length) return tileSize; | |
tileSize = x; | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.center = function(x) { | |
if (!arguments.length) return center; | |
center = x; | |
recenter(); | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.panBy = function(x) { | |
var k = 45 / Math.pow(2, zoom + zoomFraction - 3), | |
dx = x.x * k, | |
dy = x.y * k; | |
return map.center({ | |
lon: center.lon + (angleSini * dy - angleCosi * dx) / tileSize.x, | |
lat: y2lat(lat2y(center.lat) + (angleSini * dx + angleCosi * dy) / tileSize.y) | |
}); | |
}; | |
map.centerRange = function(x) { | |
if (!arguments.length) return centerRange; | |
centerRange = x; | |
if (centerRange) { | |
ymin = centerRange[0].lat > -90 ? lat2y(centerRange[0].lat) : -Infinity; | |
ymax = centerRange[0].lat < 90 ? lat2y(centerRange[1].lat) : Infinity; | |
} else { | |
ymin = -Infinity; | |
ymax = Infinity; | |
} | |
recenter(); | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.zoom = function(x) { | |
if (!arguments.length) return zoom + zoomFraction; | |
zoom = x; | |
rezoom(); | |
return map.center(center); | |
}; | |
map.zoomBy = function(z, x0, l) { | |
if (arguments.length < 2) return map.zoom(zoom + zoomFraction + z); | |
// compute the location of x0 | |
if (arguments.length < 3) l = map.pointLocation(x0); | |
// update the zoom level | |
zoom = zoom + zoomFraction + z; | |
rezoom(); | |
// compute the new point of the location | |
var x1 = map.locationPoint(l); | |
return map.panBy({x: x0.x - x1.x, y: x0.y - x1.y}); | |
}; | |
map.zoomRange = function(x) { | |
if (!arguments.length) return zoomRange; | |
zoomRange = x; | |
return map.zoom(zoom + zoomFraction); | |
}; | |
map.extent = function(x) { | |
if (!arguments.length) return [ | |
map.pointLocation({x: 0, y: sizeActual.y}), | |
map.pointLocation({x: sizeActual.x, y: 0}) | |
]; | |
// compute the extent in points, scale factor, and center | |
var bl = map.locationPoint(x[0]), | |
tr = map.locationPoint(x[1]), | |
k = Math.max((tr.x - bl.x) / sizeActual.x, (bl.y - tr.y) / sizeActual.y), | |
l = map.pointLocation({x: (bl.x + tr.x) / 2, y: (bl.y + tr.y) / 2}); | |
// update the zoom level | |
zoom = zoom + zoomFraction - Math.log(k) / Math.LN2; | |
rezoom(); | |
// set the new center | |
return map.center(l); | |
}; | |
map.angle = function(x) { | |
if (!arguments.length) return angle; | |
angle = x; | |
angleCos = Math.cos(angle); | |
angleSin = Math.sin(angle); | |
angleCosi = Math.cos(-angle); | |
angleSini = Math.sin(-angle); | |
recenter(); | |
map.dispatch({type: "move"}); | |
return map; | |
}; | |
map.add = function(x) { | |
x.map(map); | |
return map; | |
}; | |
map.remove = function(x) { | |
x.map(null); | |
return map; | |
}; | |
map.dispatch = ms.dispatch(map); | |
map.interact(true); | |
return map.resize(); // infer size | |
}; | |
function resizer(e) { | |
for (var i = 0; i < resizer.maps.length; i++) { | |
resizer.maps[i].resize(); | |
} | |
} | |
resizer.maps = []; | |
resizer.add = function(map) { | |
for (var i = 0; i < resizer.maps.length; i++) { | |
if (resizer.maps[i] == map) return; | |
} | |
resizer.maps.push(map); | |
}; | |
resizer.remove = function(map) { | |
for (var i = 0; i < resizer.maps.length; i++) { | |
if (resizer.maps[i] == map) { | |
resizer.maps.splice(i, 1); | |
return; | |
} | |
} | |
}; | |
// Note: assumes single window (no frames, iframes, etc.)! | |
if (window.addEventListener) { | |
window.addEventListener("resize", resizer, false); | |
window.addEventListener("load", resizer, false); | |
} | |
// See http://wiki.openstreetmap.org/wiki/Mercator | |
function y2lat(y) { | |
return 360 / Math.PI * Math.atan(Math.exp(y * Math.PI / 180)) - 90; | |
} | |
function lat2y(lat) { | |
return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)); | |
} | |
ms.map.locationCoordinate = function(l) { | |
var k = 1 / 360; | |
return { | |
column: (l.lon + 180) * k, | |
row: (180 - lat2y(l.lat)) * k, | |
zoom: 0 | |
}; | |
}; | |
ms.map.coordinateLocation = function(c) { | |
var k = 45 / Math.pow(2, c.zoom - 3); | |
return { | |
lon: k * c.column - 180, | |
lat: y2lat(180 - k * c.row) | |
}; | |
}; | |
// https://bugs.webkit.org/show_bug.cgi?id=44083 | |
var bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; | |
ms.layer = function(load, unload) { | |
var layer = {}, | |
cache = layer.cache = ms.cache(load, unload).size(512), | |
tile = true, | |
visible = true, | |
zoom, | |
id, | |
map, | |
container = ms.svg("g"), | |
transform, | |
levelZoom, | |
levels = {}; | |
container.setAttribute("class", "layer"); | |
for (var i = -4; i <= -1; i++) levels[i] = container.appendChild(ms.svg("g")); | |
for (var i = 2; i >= 1; i--) levels[i] = container.appendChild(ms.svg("g")); | |
levels[0] = container.appendChild(ms.svg("g")); | |
function zoomIn(z) { | |
var end = levels[0].nextSibling; | |
for (; levelZoom < z; levelZoom++) { | |
// -4, -3, -2, -1, +2, +1, =0 // current order | |
// -3, -2, -1, +2, +1, =0, -4 // insertBefore(-4, end) | |
// -3, -2, -1, +1, =0, -4, +2 // insertBefore(+2, end) | |
// -3, -2, -1, =0, -4, +2, +1 // insertBefore(+1, end) | |
// -4, -3, -2, -1, +2, +1, =0 // relabel | |
container.insertBefore(levels[-4], end); | |
container.insertBefore(levels[2], end); | |
container.insertBefore(levels[1], end); | |
var t = levels[-4]; | |
for (var dz = -4; dz < 2;) levels[dz] = levels[++dz]; | |
levels[dz] = t; | |
} | |
} | |
function zoomOut(z) { | |
var end = levels[0].nextSibling; | |
for (; levelZoom > z; levelZoom--) { | |
// -4, -3, -2, -1, +2, +1, =0 // current order | |
// -4, -3, -2, +2, +1, =0, -1 // insertBefore(-1, end) | |
// +2, -4, -3, -2, +1, =0, -1 // insertBefore(+2, -4) | |
// -4, -3, -2, -1, +2, +1, =0 // relabel | |
container.insertBefore(levels[-1], end); | |
container.insertBefore(levels[2], levels[-4]); | |
var t = levels[2]; | |
for (var dz = 2; dz > -4;) levels[dz] = levels[--dz]; | |
levels[dz] = t; | |
} | |
} | |
function move() { | |
var map = layer.map(), // in case the layer is removed | |
mapZoom = map.zoom(), | |
mapZoomFraction = mapZoom - (mapZoom = Math.round(mapZoom)), | |
mapSize = map.size(), | |
mapAngle = map.angle(), | |
tileSize = map.tileSize(), | |
tileCenter = map.locationCoordinate(map.center()); | |
// set the layer zoom levels | |
if (levelZoom != mapZoom) { | |
if (levelZoom < mapZoom) zoomIn(mapZoom); | |
else if (levelZoom > mapZoom) zoomOut(mapZoom); | |
else levelZoom = mapZoom; | |
for (var z = -4; z <= 2; z++) { | |
var l = levels[z]; | |
l.setAttribute("class", "zoom" + (z < 0 ? "" : "+") + z + " zoom" + (mapZoom + z)); | |
l.setAttribute("transform", "scale(" + Math.pow(2, -z) + ")"); | |
} | |
} | |
// get the coordinates of the four corners | |
var c0 = map.pointCoordinate(tileCenter, zero), | |
c1 = map.pointCoordinate(tileCenter, {x: mapSize.x, y: 0}), | |
c2 = map.pointCoordinate(tileCenter, mapSize), | |
c3 = map.pointCoordinate(tileCenter, {x: 0, y: mapSize.y}); | |
var col = tileCenter.column, row = tileCenter.row; | |
tileCenter.column = Math.round((Math.round(tileSize.x * tileCenter.column) + (mapSize.x & 1) / 2) / tileSize.x); | |
tileCenter.row = Math.round((Math.round(tileSize.y * tileCenter.row) + (mapSize.y & 1) / 2) / tileSize.y); | |
col -= tileCenter.column; | |
row -= tileCenter.row; | |
// set the layer transform | |
var roundedZoomFraction = roundZoom(Math.pow(2, mapZoomFraction)); | |
container.setAttribute("transform", | |
"translate(" + | |
Math.round(mapSize.x / 2 - col * tileSize.x * roundedZoomFraction) + | |
"," + | |
Math.round(mapSize.y / 2 - row * tileSize.y * roundedZoomFraction) + | |
")" + | |
(mapAngle ? "rotate(" + mapAngle / Math.PI * 180 + ")" : "") + | |
(mapZoomFraction ? "scale(" + roundedZoomFraction + ")" : "") + | |
(transform ? transform.zoomFraction(mapZoomFraction) : "")); | |
// layer-specific coordinate transform | |
if (transform) { | |
c0 = transform.unapply(c0); | |
c1 = transform.unapply(c1); | |
c2 = transform.unapply(c2); | |
c3 = transform.unapply(c3); | |
tileCenter = transform.unapply(tileCenter); | |
} | |
// layer-specific zoom transform | |
var tileLevel = zoom ? zoom(c0.zoom) - c0.zoom : 0; | |
if (tileLevel) { | |
var k = Math.pow(2, tileLevel); | |
c0.column *= k; c0.row *= k; | |
c1.column *= k; c1.row *= k; | |
c2.column *= k; c2.row *= k; | |
c3.column *= k; c3.row *= k; | |
c0.zoom = c1.zoom = c2.zoom = c3.zoom += tileLevel; | |
} | |
// tile-specific projection | |
function projection(c) { | |
var zoom = c.zoom, | |
max = zoom < 0 ? 1 : 1 << zoom, | |
column = c.column % max, | |
row = c.row; | |
if (column < 0) column += max; | |
return { | |
locationPoint: function(l) { | |
var c = ms.map.locationCoordinate(l), | |
k = Math.pow(2, zoom - c.zoom); | |
return { | |
x: tileSize.x * (k * c.column - column), | |
y: tileSize.y * (k * c.row - row) | |
}; | |
} | |
}; | |
} | |
// record which tiles are visible | |
var oldLocks = cache.locks(), newLocks = {}; | |
// reset the proxy counts | |
for (var key in oldLocks) { | |
oldLocks[key].proxyCount = 0; | |
} | |
// load the tiles! | |
if (visible && tileLevel > -5 && tileLevel < 3) { | |
var ymax = c0.zoom < 0 ? 1 : 1 << c0.zoom; | |
if (tile) { | |
scanTriangle(c0, c1, c2, 0, ymax, scanLine); | |
scanTriangle(c2, c3, c0, 0, ymax, scanLine); | |
} else { | |
var x = Math.floor((c0.column + c2.column) / 2), | |
y = Math.max(0, Math.min(ymax - 1, Math.floor((c1.row + c3.row) / 2))), | |
z = Math.min(4, c0.zoom); | |
x = x >> z << z; | |
y = y >> z << z; | |
scanLine(x, x + 1, y); | |
} | |
} | |
// scan-line conversion | |
function scanLine(x0, x1, y) { | |
var z = c0.zoom, | |
z0 = 2 - tileLevel, | |
z1 = 4 + tileLevel; | |
for (var x = x0; x < x1; x++) { | |
var t = cache.load({column: x, row: y, zoom: z}, projection); | |
if (!t.ready && !(t.key in newLocks)) { | |
t.proxyRefs = {}; | |
var c, full, proxy; | |
// downsample high-resolution tiles | |
for (var dz = 1; dz <= z0; dz++) { | |
full = true; | |
for (var dy = 0, k = 1 << dz; dy <= k; dy++) { | |
for (var dx = 0; dx <= k; dx++) { | |
proxy = cache.peek(c = { | |
column: (x << dz) + dx, | |
row: (y << dz) + dy, | |
zoom: z + dz | |
}); | |
if (proxy && proxy.ready) { | |
newLocks[proxy.key] = cache.load(c); | |
proxy.proxyCount++; | |
t.proxyRefs[proxy.key] = proxy; | |
} else { | |
full = false; | |
} | |
} | |
} | |
if (full) break; | |
} | |
// upsample low-resolution tiles | |
if (!full) { | |
for (var dz = 1; dz <= z1; dz++) { | |
proxy = cache.peek(c = { | |
column: x >> dz, | |
row: y >> dz, | |
zoom: z - dz | |
}); | |
if (proxy && proxy.ready) { | |
newLocks[proxy.key] = cache.load(c); | |
proxy.proxyCount++; | |
t.proxyRefs[proxy.key] = proxy; | |
break; | |
} | |
} | |
} | |
} | |
newLocks[t.key] = t; | |
} | |
} | |
function roundZoom(z) { | |
return Math.round(z * 256) / 256; | |
} | |
// position tiles | |
for (var key in newLocks) { | |
var t = newLocks[key], | |
k = roundZoom(Math.pow(2, t.level = t.zoom - tileCenter.zoom)); | |
var transform = "translate(" + | |
Math.round(t.x = tileSize.x * (t.column - tileCenter.column * k)) + "px" + "," + | |
Math.round(t.y = tileSize.y * (t.row - tileCenter.row * k)) + "px" + ")"; | |
d3.select(t.element).style("transform", transform); | |
d3.select(t.element).style("-webkit-transform", transform); | |
d3.select(t.element).style("-ms-transform", transform); | |
} | |
// remove tiles that are no longer visible | |
for (var key in oldLocks) { | |
if (!(key in newLocks)) { | |
var t = cache.unload(key); | |
t.element.parentNode.removeChild(t.element); | |
delete t.proxyRefs; | |
} | |
} | |
// append tiles that are now visible | |
for (var key in newLocks) { | |
var t = newLocks[key]; | |
if (t.element.parentNode != levels[t.level]) { | |
levels[t.level].appendChild(t.element); | |
if (layer.show) layer.show(t); | |
} | |
} | |
// flush the cache, clearing no-longer-needed tiles | |
cache.flush(); | |
// dispatch the move event | |
layer.dispatch({type: "move"}); | |
} | |
// remove proxy tiles when tiles load | |
function cleanup(e) { | |
if (e.tile.proxyRefs) { | |
for (var proxyKey in e.tile.proxyRefs) { | |
var proxyTile = e.tile.proxyRefs[proxyKey]; | |
if ((--proxyTile.proxyCount <= 0) && cache.unload(proxyKey)) { | |
proxyTile.element.parentNode.removeChild(proxyTile.element); | |
} | |
} | |
delete e.tile.proxyRefs; | |
} | |
} | |
layer.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
if (map == x) { | |
container.parentNode.appendChild(container); // move to end | |
return layer; | |
} | |
map.off("move", move).off("resize", move); | |
container.parentNode.removeChild(container); | |
} | |
map = x; | |
if (map) { | |
map.svgContainer().appendChild(container); | |
if (layer.init) layer.init(container); | |
map.on("move", move).on("resize", move); | |
move(); | |
} | |
return layer; | |
}; | |
layer.container = function() { | |
return container; | |
}; | |
layer.levels = function() { | |
return levels; | |
}; | |
layer.id = function(x) { | |
if (!arguments.length) return id; | |
id = x; | |
container.setAttribute("id", x); | |
return layer; | |
}; | |
layer.visible = function(x) { | |
if (!arguments.length) return visible; | |
if (visible = x) container.removeAttribute("visibility"); | |
else container.setAttribute("visibility", "hidden"); | |
if (map) move(); | |
return layer; | |
}; | |
layer.transform = function(x) { | |
if (!arguments.length) return transform; | |
transform = x; | |
if (map) move(); | |
return layer; | |
}; | |
layer.zoom = function(x) { | |
if (!arguments.length) return zoom; | |
zoom = typeof x == "function" || x == null ? x : function() { return x; }; | |
if (map) move(); | |
return layer; | |
}; | |
layer.tile = function(x) { | |
if (!arguments.length) return tile; | |
tile = x; | |
if (map) move(); | |
return layer; | |
}; | |
layer.reload = function() { | |
cache.clear(); | |
if (map) move(); | |
return layer; | |
}; | |
layer.dispatch = ms.dispatch(layer); | |
layer.on("load", cleanup); | |
return layer; | |
}; | |
// scan-line conversion | |
function edge(a, b) { | |
if (a.row > b.row) { var t = a; a = b; b = t; } | |
return { | |
x0: a.column, | |
y0: a.row, | |
x1: b.column, | |
y1: b.row, | |
dx: b.column - a.column, | |
dy: b.row - a.row | |
}; | |
} | |
// scan-line conversion | |
function scanSpans(e0, e1, ymin, ymax, scanLine) { | |
var y0 = Math.max(ymin, Math.floor(e1.y0)), | |
y1 = Math.min(ymax, Math.ceil(e1.y1)); | |
// sort edges by x-coordinate | |
if ((e0.x0 == e1.x0 && e0.y0 == e1.y0) ? | |
(e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1) : | |
(e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) { | |
var t = e0; e0 = e1; e1 = t; | |
} | |
// scan lines! | |
var m0 = e0.dx / e0.dy, | |
m1 = e1.dx / e1.dy, | |
d0 = e0.dx > 0, // use y + 1 to compute x0 | |
d1 = e1.dx < 0; // use y + 1 to compute x1 | |
for (var y = y0; y < y1; y++) { | |
var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0, | |
x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0; | |
scanLine(Math.floor(x1), Math.ceil(x0), y); | |
} | |
} | |
// scan-line conversion | |
function scanTriangle(a, b, c, ymin, ymax, scanLine) { | |
var ab = edge(a, b), | |
bc = edge(b, c), | |
ca = edge(c, a); | |
// sort edges by y-length | |
if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; } | |
if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; } | |
if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; } | |
// scan span! scan span! | |
if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine); | |
if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine); | |
} | |
ms.image = function() { | |
var image = ms.layer(load, unload), | |
url; | |
function load(tile) { | |
var element = tile.element = ms.svg("image"), size = image.map().tileSize(); | |
element.setAttribute("preserveAspectRatio", "none"); | |
element.setAttribute("width", size.x); | |
element.setAttribute("height", size.y); | |
if (typeof url == "function") { | |
element.setAttribute("opacity", 0); | |
var tileUrl = url(tile); | |
if (tileUrl != null) { | |
tile.request = ms.queue.image(element, tileUrl, function(img) { | |
delete tile.request; | |
tile.ready = true; | |
tile.img = img; | |
element.removeAttribute("opacity"); | |
image.dispatch({type: "load", tile: tile}); | |
}); | |
} else { | |
tile.ready = true; | |
image.dispatch({type: "load", tile: tile}); | |
} | |
} else { | |
tile.ready = true; | |
if (url != null) element.setAttributeNS(ms.ns.xlink, "href", url); | |
image.dispatch({type: "load", tile: tile}); | |
} | |
} | |
function unload(tile) { | |
if (tile.request) tile.request.abort(true); | |
} | |
image.url = function(x) { | |
if (!arguments.length) return url; | |
url = typeof x == "string" && /{.}/.test(x) ? ms.url(x) : x; | |
return image.reload(); | |
}; | |
return image; | |
}; | |
ms.geoJson = function(fetch) { | |
var geoJson = ms.layer(load, unload), | |
container = geoJson.container(), | |
url, | |
clip = false, | |
clipId = "org.polymaps." + ms.id(), | |
clipHref = "url(#" + clipId + ")", | |
clipPath = container.insertBefore(ms.svg("clipPath"), container.firstChild), | |
clipRect = clipPath.appendChild(ms.svg("rect")), | |
scale = "fixed", | |
zoom = null, | |
pointRadius = 4.5, | |
features, | |
tileBackground = false, | |
transformData, | |
key = (function() { | |
var k = 0; | |
return function(f) { | |
k = k + 1 | 0; | |
return k; | |
}; | |
})(), | |
selection, | |
dataGeneration = 0, | |
selectionGeneration = 0; | |
container.setAttribute("fill-rule", "evenodd"); | |
clipPath.setAttribute("id", clipId); | |
if (!arguments.length) fetch = ms.queue.json; | |
function projection(proj) { | |
var l = {lat: 0, lon: 0}; | |
return function(coordinates, c) { | |
l.lat = coordinates[1]; | |
l.lon = coordinates[0]; | |
var p = proj(l); | |
c.x = p.x; | |
c.y = p.y; | |
return p; | |
}; | |
} | |
function rescale(o, e, k) { | |
var g = o.geometry; | |
return g.type in rescales && rescales[g.type](o, e, k); | |
} | |
var rescales = { | |
Point: function (o, e, k) { | |
e.setAttribute("transform", "translate(" + o.x + "," + o.y + ")" + k); | |
}, | |
MultiPoint: function (o, e, k) { | |
e.setAttribute("transform", "translate(" + o.x + "," + o.y + ")" + k); | |
} | |
}; | |
// Create path projecting WGS84 spherical Mercator coordinates. | |
function projectSpherical(tileProj) { | |
return d3.geo.path().projection({ | |
stream: function(stream) { | |
return { | |
point: function(x, y) { | |
// Latitudes at the poles (or beyond!) result in unrenderable NaN's and Infinities. | |
var epsilon = 1.0e-6; | |
y = Math.min(90 - epsilon, y); | |
y = Math.max(-90 + epsilon, y); | |
var p = tileProj.locationPoint({ lon: x, lat: y }); | |
stream.point(Math.round(2 * p.x) / 2, Math.round(2 * p.y) / 2); | |
}, | |
sphere: function() { stream.sphere(); }, | |
lineStart: function() { stream.lineStart(); }, | |
lineEnd: function() { stream.lineEnd(); }, | |
polygonStart: function() { stream.polygonStart(); }, | |
polygonEnd: function() { stream.polygonEnd(); } | |
}; | |
} | |
}); | |
} | |
function load(tile, proj) { | |
var g = tile.element = ms.svg("g"); | |
tile.proj = proj(tile); | |
tile.fetched = []; | |
tile.features = []; | |
tile.draw = function() { | |
draw(g, tile); | |
}; | |
function update(data) { | |
if (geoJson.tile() && tileBackground) { | |
var tileSize = geoJson.map().tileSize(); | |
d3.select(g.insertBefore(ms.svg("rect"), g.firstChild)) | |
.attr("width", tileSize.x) | |
.attr("height", tileSize.x) | |
.attr("class", "tile-background"); | |
} | |
tile.fetched = data.features; | |
tile.draw(); | |
tile.ready = true; | |
geoJson.dispatch({type: "load", tile: tile, features: tile.features}); | |
} | |
if (url != null) { | |
tile.request = fetch(typeof url == "function" ? url(tile) : url, update); | |
} else { | |
update({type: "FeatureCollection", features: features || []}); | |
} | |
} | |
function copyObject(source) { | |
var copy = {}; | |
for (var property in source) { | |
copy[property] = source[property]; | |
} | |
return copy; | |
} | |
function projectPoint(p, proj) { | |
proj(p.geometry.coordinates, p); | |
return p; | |
} | |
function projectPointsForMultiPoint(mp, proj) { | |
var length = mp.geometry.coordinates.length; | |
var points = []; | |
for (var i = 0; i < length; i++) { | |
var p = copyObject(mp); | |
proj(p.geometry.coordinates[i], p); | |
points.push(p); | |
} | |
return points; | |
} | |
function draw(g, tile) { | |
var proj = projection(tile.proj.locationPoint), | |
path = projectSpherical(tile.proj), | |
pathFeatures = [], | |
pointFeatures = [], | |
features = transformData ? transformData(tile.fetched, tile) : tile.fetched, | |
updated = []; | |
features.forEach(function(f) { | |
if (f.geometry.type === "Point") | |
pointFeatures.push(projectPoint(f, proj)); | |
else if (f.geometry.type === "MultiPoint") | |
pointFeatures.push.apply(pointFeatures, projectPointsForMultiPoint(f, proj)); | |
else | |
pathFeatures.push(f); | |
}); | |
var pathUpdate = d3.select(g) | |
.selectAll("path") | |
.data(pathFeatures, key); | |
pathUpdate.enter() | |
.append("path") | |
.attr("d", function(f) { return path(f); }); | |
pathUpdate.exit().remove(); | |
pathUpdate.each(function(f) { updated.push({ element: this, data: f }); }); | |
var initialScale = ""; | |
if (scale == "fixed") { | |
initialScale = "scale(" + Math.pow(2, tile.zoom - (tile.scale = geoJson.map().zoom())) + ")"; | |
} | |
var pointUpdate = d3.select(g) | |
.selectAll("circle") | |
.data(pointFeatures, key); | |
pointUpdate.enter() | |
.append("circle") | |
.attr("transform", function(f) { | |
return "translate(" + f.x + "," + f.y + ")" + initialScale; | |
}) | |
.attr("r", pointRadius); | |
pointUpdate.exit().remove(); | |
pointUpdate.each(function(f) { updated.push({ element: this, data: f }); }); | |
if (selection) { | |
pathUpdate.push.apply(pathUpdate, pointUpdate); | |
selection(pathUpdate); | |
} | |
tile.features = updated; | |
tile.dataGeneration = dataGeneration; | |
tile.selectionGeneration = selectionGeneration; | |
} | |
function unload(tile) { | |
if (tile.request) tile.request.abort(true); | |
} | |
function move() { | |
var zoom = geoJson.map().zoom(), | |
tiles = geoJson.cache.locks(), // visible tiles | |
key, // key in locks | |
tile, // locks[key] | |
features, // tile.features | |
i, // current feature index | |
n, // current feature count, features.length | |
feature, // features[i] | |
k; // scale transform | |
if (scale == "fixed") { | |
for (key in tiles) { | |
if ((tile = tiles[key]).scale != zoom) { | |
k = "scale(" + Math.pow(2, tile.zoom - zoom) + ")"; | |
i = -1; | |
n = (features = tile.features).length; | |
while (++i < n) rescale((feature = features[i]).data, feature.element, k); | |
tile.scale = zoom; | |
} | |
} | |
} | |
} | |
geoJson.tileBackground = function(x) { | |
if (!arguments.length) return tileBackground; | |
tileBackground = x; | |
return geoJson; | |
}; | |
geoJson.selection = function(x) { | |
if (!arguments.length) return selection; | |
selection = x; | |
selectionGeneration = selectionGeneration + 1 | 0; | |
return geoJson.reshow(); | |
}; | |
geoJson.transformData = function(x) { | |
if (!arguments.length) return transformData; | |
transformData = x; | |
dataGeneration = dataGeneration + 1 | 0; | |
return geoJson.reshow(); | |
}; | |
geoJson.url = function(x) { | |
if (!arguments.length) return url; | |
url = typeof x == "string" && /{.}/.test(x) ? ms.url(x) : x; | |
if (url != null) features = null; | |
if (typeof url == "string") geoJson.tile(false); | |
return geoJson.reload(); | |
}; | |
geoJson.features = function(x) { | |
if (!arguments.length) return features; | |
if (features = x) { | |
url = null; | |
geoJson.tile(false); | |
} | |
return geoJson.reload(); | |
}; | |
geoJson.clip = function(x) { | |
if (!arguments.length) return clip; | |
if (clip) container.removeChild(clipPath); | |
if (clip = x) container.insertBefore(clipPath, container.firstChild); | |
var locks = geoJson.cache.locks(); | |
for (var key in locks) { | |
if (clip) locks[key].element.setAttribute("clip-path", clipHref); | |
else locks[key].element.removeAttribute("clip-path"); | |
} | |
return geoJson; | |
}; | |
var __tile__ = geoJson.tile; | |
geoJson.tile = function(x) { | |
if (arguments.length && !x) geoJson.clip(x); | |
return __tile__.apply(geoJson, arguments); | |
}; | |
var __map__ = geoJson.map; | |
geoJson.map = function(x) { | |
if (x && clipRect) { | |
var size = x.tileSize(); | |
clipRect.setAttribute("width", size.x); | |
clipRect.setAttribute("height", size.y); | |
} | |
return __map__.apply(geoJson, arguments); | |
}; | |
geoJson.scale = function(x) { | |
if (!arguments.length) return scale; | |
if (scale = x) geoJson.on("move", move); | |
else geoJson.off("move", move); | |
if (geoJson.map()) move(); | |
return geoJson; | |
}; | |
geoJson.show = function(tile) { | |
if (clip) tile.element.setAttribute("clip-path", clipHref); | |
else tile.element.removeAttribute("clip-path"); | |
if (transformData && tile.dataGeneration !== dataGeneration) { | |
tile.draw(); | |
} | |
else if (selection && tile.selectionGeneration !== selectionGeneration) { | |
var s = d3.select(tile.element).selectAll("path,circle"); | |
selection(s); | |
tile.selectionGeneration = selectionGeneration; | |
} | |
geoJson.dispatch({type: "show", tile: tile, features: tile.features}); | |
return geoJson; | |
}; | |
geoJson.reshow = function() { | |
var locks = geoJson.cache.locks(); | |
for (var key in locks) geoJson.show(locks[key]); | |
return geoJson; | |
}; | |
return geoJson; | |
}; | |
ms.topoJson = function(fetch) { | |
if (!arguments.length) fetch = ms.queue.json; | |
var classify, | |
staticTopology; | |
function groupFeatures(features) { | |
if (!classify) | |
return features; | |
var classIndices = {}; | |
var groupedFeatures = []; | |
features.forEach(function(f) { | |
var c = classify(f); | |
var index = classIndices[c]; | |
if (index === undefined) { | |
index = groupedFeatures.push([]) - 1; | |
classIndices[c] = index; | |
} | |
groupedFeatures[index].push(f); | |
}); | |
return groupedFeatures.map(function(g) { | |
return { | |
type: 'GeometryCollection', | |
geometries: g | |
}; | |
}); | |
} | |
var topologyFeatures = function(topology) { | |
function convert(topology, object, layer, features) { | |
var featureOrCollection = topojson.feature(topology, object), | |
layerFeatures; | |
if (featureOrCollection.type === "FeatureCollection") { | |
layerFeatures = featureOrCollection.features; | |
} else { | |
layerFeatures = [featureOrCollection]; | |
} | |
layerFeatures.forEach(function(f) { | |
f.properties.layer = layer; | |
}); | |
features.push.apply(features, layerFeatures); | |
} | |
var features = []; | |
for (var o in topology.objects) { | |
convert(topology, topology.objects[o], o, features); | |
} | |
features = groupFeatures(features); | |
return features; | |
}; | |
var topoToGeo = function(url, callback) { | |
return fetch(url, function(topology) { | |
callback({ | |
type: "FeatureCollection", | |
features: topologyFeatures(topology) | |
}); | |
}); | |
}; | |
var topoJson = ms.geoJson(topoToGeo); | |
topoJson.topologyFeatures = function(x) { | |
if (!arguments.length) return topologyFeatures; | |
topologyFeatures = x; | |
return topoJson; | |
}; | |
topoJson.classify = function(x) { | |
if (!arguments.length) return classify; | |
classify = x; | |
return topoJson; | |
}; | |
topoJson.staticTopology = function(x) { | |
if (!arguments.length) return staticTopology; | |
staticTopology = x; | |
return topoJson.features(staticTopology ? topologyFeatures(staticTopology) : null); | |
}; | |
return topoJson; | |
}; | |
ms.dblclick = function() { | |
var dblclick = {}, | |
zoom = "mouse", | |
map, | |
container; | |
function handle(e) { | |
var z = map.zoom(); | |
if (e.shiftKey) z = Math.ceil(z) - z - 1; | |
else z = 1 - z + Math.floor(z); | |
if (zoom === "mouse") | |
map.zoomBy(z, map.mouse(e)); | |
else | |
map.zoomBy(z); | |
} | |
dblclick.zoom = function(x) { | |
if (!arguments.length) return zoom; | |
zoom = x; | |
return dblclick; | |
}; | |
dblclick.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("dblclick", handle, false); | |
container = null; | |
} | |
if (map = x) { | |
container = map.container(); | |
container.addEventListener("dblclick", handle, false); | |
} | |
return dblclick; | |
}; | |
return dblclick; | |
}; | |
ms.drag = function() { | |
var drag = {}, | |
map, | |
container, | |
dragging; | |
function mousedown(e) { | |
if (e.shiftKey) return; | |
dragging = { | |
x: e.clientX, | |
y: e.clientY | |
}; | |
map.focusableParent().focus(); | |
e.preventDefault(); | |
document.body.style.setProperty("cursor", "move", null); | |
} | |
function mousemove(e) { | |
if (!dragging) return; | |
map.panBy({x: e.clientX - dragging.x, y: e.clientY - dragging.y}); | |
dragging.x = e.clientX; | |
dragging.y = e.clientY; | |
} | |
function mouseup(e) { | |
if (!dragging) return; | |
mousemove(e); | |
dragging = null; | |
document.body.style.removeProperty("cursor"); | |
} | |
drag.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("mousedown", mousedown, false); | |
container = null; | |
} | |
if (map = x) { | |
container = map.container(); | |
container.addEventListener("mousedown", mousedown, false); | |
} | |
return drag; | |
}; | |
window.addEventListener("mousemove", mousemove, false); | |
window.addEventListener("mouseup", mouseup, false); | |
return drag; | |
}; | |
ms.wheel = function() { | |
var wheel = {}, | |
timePrev = 0, | |
last = 0, | |
smooth = true, | |
zoom = "mouse", | |
location, | |
map, | |
container; | |
function move(e) { | |
location = null; | |
} | |
// mousewheel events are totally broken! | |
// https://bugs.webkit.org/show_bug.cgi?id=40441 | |
// not only that, but Chrome and Safari differ in re. to acceleration! | |
var inner = document.createElement("div"), | |
outer = document.createElement("div"); | |
outer.style.visibility = "hidden"; | |
outer.style.top = "0px"; | |
outer.style.height = "0px"; | |
outer.style.width = "0px"; | |
outer.style.overflowY = "scroll"; | |
inner.style.height = "2000px"; | |
outer.appendChild(inner); | |
document.body.appendChild(outer); | |
function mousewheel(e) { | |
var delta = e.wheelDelta || -e.detail, | |
point; | |
/* Detect the pixels that would be scrolled by this wheel event. */ | |
if (delta) { | |
if (smooth) { | |
try { | |
outer.scrollTop = 1000; | |
outer.dispatchEvent(e); | |
delta = 1000 - outer.scrollTop; | |
} catch (error) { | |
// Derp! Hope for the best? | |
} | |
delta *= 0.001; | |
} | |
/* If smooth zooming is disabled, batch events into unit steps. */ | |
else { | |
var timeNow = Date.now(); | |
if (timeNow - timePrev > 200) { | |
delta = delta > 0 ? +1 : -1; | |
timePrev = timeNow; | |
} else { | |
delta = 0; | |
} | |
} | |
} | |
if (delta) { | |
switch (zoom) { | |
case "mouse": { | |
point = map.mouse(e); | |
if (!location) location = map.pointLocation(point); | |
map.off("move", move).zoomBy(delta, point, location).on("move", move); | |
break; | |
} | |
case "location": { | |
map.zoomBy(delta, map.locationPoint(location), location); | |
break; | |
} | |
default: { // center | |
map.zoomBy(delta); | |
break; | |
} | |
} | |
} | |
e.preventDefault(); | |
return false; // for Firefox | |
} | |
wheel.smooth = function(x) { | |
if (!arguments.length) return smooth; | |
smooth = x; | |
return wheel; | |
}; | |
wheel.zoom = function(x, l) { | |
if (!arguments.length) return zoom; | |
zoom = x; | |
location = l; | |
if (map) { | |
if (zoom == "mouse") map.on("move", move); | |
else map.off("move", move); | |
} | |
return wheel; | |
}; | |
wheel.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("mousemove", move, false); | |
container.removeEventListener("mousewheel", mousewheel, false); | |
container.removeEventListener("MozMousePixelScroll", mousewheel, false); | |
container = null; | |
map.off("move", move); | |
} | |
if (map = x) { | |
if (zoom == "mouse") map.on("move", move); | |
container = map.container(); | |
container.addEventListener("mousemove", move, false); | |
container.addEventListener("mousewheel", mousewheel, false); | |
container.addEventListener("MozMousePixelScroll", mousewheel, false); | |
} | |
return wheel; | |
}; | |
return wheel; | |
}; | |
ms.arrow = function() { | |
var arrow = {}, | |
key = {left: 0, right: 0, up: 0, down: 0}, | |
last = 0, | |
repeatTimer, | |
repeatDelay = 250, | |
repeatInterval = 50, | |
speed = 16, | |
map, | |
parent; | |
function keydown(e) { | |
if (e.ctrlKey || e.altKey || e.metaKey) return; | |
var now = Date.now(), dx = 0, dy = 0; | |
switch (e.keyCode) { | |
case 37: { | |
if (!key.left) { | |
last = now; | |
key.left = 1; | |
if (!key.right) dx = speed; | |
} | |
break; | |
} | |
case 39: { | |
if (!key.right) { | |
last = now; | |
key.right = 1; | |
if (!key.left) dx = -speed; | |
} | |
break; | |
} | |
case 38: { | |
if (!key.up) { | |
last = now; | |
key.up = 1; | |
if (!key.down) dy = speed; | |
} | |
break; | |
} | |
case 40: { | |
if (!key.down) { | |
last = now; | |
key.down = 1; | |
if (!key.up) dy = -speed; | |
} | |
break; | |
} | |
default: return; | |
} | |
if (dx || dy) map.panBy({x: dx, y: dy}); | |
if (!repeatTimer && (key.left | key.right | key.up | key.down)) { | |
repeatTimer = setInterval(repeat, repeatInterval); | |
} | |
e.preventDefault(); | |
} | |
function keyup(e) { | |
last = Date.now(); | |
switch (e.keyCode) { | |
case 37: key.left = 0; break; | |
case 39: key.right = 0; break; | |
case 38: key.up = 0; break; | |
case 40: key.down = 0; break; | |
default: return; | |
} | |
if (repeatTimer && !(key.left | key.right | key.up | key.down)) { | |
repeatTimer = clearInterval(repeatTimer); | |
} | |
e.preventDefault(); | |
} | |
function keypress(e) { | |
switch (e.charCode) { | |
case 45: case 95: map.zoom(Math.ceil(map.zoom()) - 1); break; // - _ | |
case 43: case 61: map.zoom(Math.floor(map.zoom()) + 1); break; // = + | |
default: return; | |
} | |
e.preventDefault(); | |
} | |
function repeat() { | |
if (!map) return; | |
if (Date.now() < last + repeatDelay) return; | |
var dx = (key.left - key.right) * speed, | |
dy = (key.up - key.down) * speed; | |
if (dx || dy) map.panBy({x: dx, y: dy}); | |
} | |
arrow.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
parent.removeEventListener("keypress", keypress, false); | |
parent.removeEventListener("keydown", keydown, false); | |
parent.removeEventListener("keyup", keyup, false); | |
parent = null; | |
} | |
if (map = x) { | |
parent = map.focusableParent(); | |
parent.addEventListener("keypress", keypress, false); | |
parent.addEventListener("keydown", keydown, false); | |
parent.addEventListener("keyup", keyup, false); | |
} | |
return arrow; | |
}; | |
arrow.speed = function(x) { | |
if (!arguments.length) return speed; | |
speed = x; | |
return arrow; | |
}; | |
return arrow; | |
}; | |
ms.hash = function() { | |
var hash = {}, | |
s0, // cached location.hash | |
lat = 90 - 1e-8, // allowable latitude range | |
map; | |
var parser = function(map, s) { | |
var args = s.split("/").map(Number); | |
if (args.length < 3 || args.some(isNaN)) return true; // replace bogus hash | |
else { | |
var size = map.size(); | |
map.zoomBy(args[0] - map.zoom(), | |
{x: size.x / 2, y: size.y / 2}, | |
{lat: Math.min(lat, Math.max(-lat, args[1])), lon: args[2]}); | |
} | |
}; | |
var formatter = function(map) { | |
var center = map.center(), | |
zoom = map.zoom(), | |
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); | |
return "#" + zoom.toFixed(2) + | |
"/" + center.lat.toFixed(precision) + | |
"/" + center.lon.toFixed(precision); | |
}; | |
function move() { | |
var s1 = formatter(map); | |
if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! | |
} | |
function hashchange() { | |
if (location.hash === s0) return; // ignore spurious hashchange events | |
if (parser(map, (s0 = location.hash).substring(1))) | |
move(); // replace bogus hash | |
} | |
hash.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
map.off("move", move); | |
window.removeEventListener("hashchange", hashchange, false); | |
} | |
if (map = x) { | |
map.on("move", move); | |
window.addEventListener("hashchange", hashchange, false); | |
if (location.hash) | |
hashchange(); | |
else | |
move(); | |
} | |
return hash; | |
}; | |
hash.parser = function(x) { | |
if (!arguments.length) return parser; | |
parser = x; | |
return hash; | |
}; | |
hash.formatter = function(x) { | |
if (!arguments.length) return formatter; | |
formatter = x; | |
return hash; | |
}; | |
return hash; | |
}; | |
ms.touch = function() { | |
var touch = {}, | |
map, | |
container, | |
rotate = false, | |
last = 0, | |
zoom, | |
angle, | |
locations = {}; // touch identifier -> location | |
window.addEventListener("touchmove", touchmove, false); | |
function touchstart(e) { | |
var i = -1, | |
n = e.touches.length, | |
t = Date.now(); | |
// doubletap detection | |
if ((n == 1) && (t - last < 300)) { | |
var z = map.zoom(); | |
map.zoomBy(1 - z + Math.floor(z), map.mouse(e.touches[0])); | |
e.preventDefault(); | |
} | |
last = t; | |
// store original zoom & touch locations | |
zoom = map.zoom(); | |
angle = map.angle(); | |
while (++i < n) { | |
t = e.touches[i]; | |
locations[t.identifier] = map.pointLocation(map.mouse(t)); | |
} | |
} | |
function touchmove(e) { | |
switch (e.touches.length) { | |
case 1: { // single-touch pan | |
var t0 = e.touches[0]; | |
map.zoomBy(0, map.mouse(t0), locations[t0.identifier]); | |
e.preventDefault(); | |
break; | |
} | |
case 2: { // double-touch pan + zoom + rotate | |
var t0 = e.touches[0], | |
t1 = e.touches[1], | |
p0 = map.mouse(t0), | |
p1 = map.mouse(t1), | |
p2 = {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2}, // center point | |
c0 = ms.map.locationCoordinate(locations[t0.identifier]), | |
c1 = ms.map.locationCoordinate(locations[t1.identifier]), | |
c2 = {row: (c0.row + c1.row) / 2, column: (c0.column + c1.column) / 2, zoom: 0}, | |
l2 = ms.map.coordinateLocation(c2); // center location | |
map.zoomBy(Math.log(e.scale) / Math.LN2 + zoom - map.zoom(), p2, l2); | |
if (rotate) map.angle(e.rotation / 180 * Math.PI + angle); | |
e.preventDefault(); | |
break; | |
} | |
} | |
} | |
touch.rotate = function(x) { | |
if (!arguments.length) return rotate; | |
rotate = x; | |
return touch; | |
}; | |
touch.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("touchstart", touchstart, false); | |
container = null; | |
} | |
if (map = x) { | |
container = map.container(); | |
container.addEventListener("touchstart", touchstart, false); | |
} | |
return touch; | |
}; | |
return touch; | |
}; | |
// Default map controls. | |
ms.interact = function() { | |
var interact = {}, | |
drag = ms.drag(), | |
wheel = ms.wheel(), | |
dblclick = ms.dblclick(), | |
touch = ms.touch(), | |
arrow = ms.arrow(); | |
interact.map = function(x) { | |
drag.map(x); | |
wheel.map(x); | |
dblclick.map(x); | |
touch.map(x); | |
arrow.map(x); | |
return interact; | |
}; | |
return interact; | |
}; | |
ms.compass = function() { | |
var compass = {}, | |
g = ms.svg("g"), | |
ticks = {}, | |
r = 30, | |
speed = 16, | |
last = 0, | |
repeatDelay = 250, | |
repeatInterval = 50, | |
position = "top-left", // top-left, top-right, bottom-left, bottom-right | |
zoomStyle = "small", // none, small, big | |
zoomContainer, | |
panStyle = "small", // none, small | |
panTimer, | |
panDirection, | |
panContainer, | |
drag, | |
dragRect = ms.svg("rect"), | |
map, | |
container, | |
window; | |
g.setAttribute("class", "compass"); | |
dragRect.setAttribute("class", "back fore"); | |
dragRect.setAttribute("pointer-events", "none"); | |
dragRect.setAttribute("display", "none"); | |
function panStart(e) { | |
g.setAttribute("class", "compass active"); | |
if (!panTimer) panTimer = setInterval(panRepeat, repeatInterval); | |
if (panDirection) map.panBy(panDirection); | |
last = Date.now(); | |
return cancel(e); | |
} | |
function panRepeat() { | |
if (panDirection && (Date.now() > last + repeatDelay)) { | |
map.panBy(panDirection); | |
} | |
} | |
function mousedown(e) { | |
if (e.shiftKey) { | |
drag = {x0: map.mouse(e)}; | |
map.focusableParent().focus(); | |
return cancel(e); | |
} | |
} | |
function mousemove(e) { | |
if (!drag) return; | |
drag.x1 = map.mouse(e); | |
dragRect.setAttribute("x", Math.min(drag.x0.x, drag.x1.x)); | |
dragRect.setAttribute("y", Math.min(drag.x0.y, drag.x1.y)); | |
dragRect.setAttribute("width", Math.abs(drag.x0.x - drag.x1.x)); | |
dragRect.setAttribute("height", Math.abs(drag.x0.y - drag.x1.y)); | |
dragRect.removeAttribute("display"); | |
} | |
function mouseup(e) { | |
g.setAttribute("class", "compass"); | |
if (drag) { | |
if (drag.x1) { | |
map.extent([ | |
map.pointLocation({ | |
x: Math.min(drag.x0.x, drag.x1.x), | |
y: Math.max(drag.x0.y, drag.x1.y) | |
}), | |
map.pointLocation({ | |
x: Math.max(drag.x0.x, drag.x1.x), | |
y: Math.min(drag.x0.y, drag.x1.y) | |
}) | |
]); | |
dragRect.setAttribute("display", "none"); | |
} | |
drag = null; | |
} | |
if (panTimer) { | |
clearInterval(panTimer); | |
panTimer = 0; | |
} | |
} | |
function panBy(x) { | |
return function() { | |
if (x) | |
this.setAttribute("class", "active"); | |
else | |
this.removeAttribute("class"); | |
panDirection = x; | |
}; | |
} | |
function zoomBy(x) { | |
return function(e) { | |
g.setAttribute("class", "compass active"); | |
var z = map.zoom(); | |
map.zoom(x < 0 ? Math.ceil(z) - 1 : Math.floor(z) + 1); | |
return cancel(e); | |
}; | |
} | |
function zoomTo(x) { | |
return function(e) { | |
map.zoom(x); | |
return cancel(e); | |
}; | |
} | |
function zoomOver() { | |
this.setAttribute("class", "active"); | |
} | |
function zoomOut() { | |
this.removeAttribute("class"); | |
} | |
function cancel(e) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
return false; | |
} | |
function pan(by) { | |
var x = Math.SQRT1_2 * r, | |
y = r * 0.7, | |
z = r * 0.2, | |
g = ms.svg("g"), | |
dir = g.appendChild(ms.svg("path")), | |
chv = g.appendChild(ms.svg("path")); | |
dir.setAttribute("class", "direction"); | |
dir.setAttribute("pointer-events", "all"); | |
dir.setAttribute("d", "M0,0L" + x + "," + x + "A" + r + "," + r + " 0 0,1 " + -x + "," + x + "Z"); | |
chv.setAttribute("class", "chevron"); | |
chv.setAttribute("d", "M" + z + "," + (y - z) + "L0," + y + " " + -z + "," + (y - z)); | |
chv.setAttribute("pointer-events", "none"); | |
g.addEventListener("mousedown", panStart, false); | |
g.addEventListener("mouseover", panBy(by), false); | |
g.addEventListener("mouseout", panBy(null), false); | |
g.addEventListener("dblclick", cancel, false); | |
return g; | |
} | |
function zoom(by) { | |
var x = r * 0.4, | |
y = x / 2, | |
g = ms.svg("g"), | |
back = g.appendChild(ms.svg("path")), | |
dire = g.appendChild(ms.svg("path")), | |
chev = g.appendChild(ms.svg("path")), | |
fore = g.appendChild(ms.svg("path")); | |
back.setAttribute("class", "back"); | |
back.setAttribute("d", "M" + -x + ",0V" + -x + "A" + x + "," + x + " 0 1,1 " + x + "," + -x + "V0Z"); | |
dire.setAttribute("class", "direction"); | |
dire.setAttribute("d", back.getAttribute("d")); | |
chev.setAttribute("class", "chevron"); | |
chev.setAttribute("d", "M" + -y + "," + -x + "H" + y + (by > 0 ? "M0," + (-x - y) + "V" + -y : "")); | |
fore.setAttribute("class", "fore"); | |
fore.setAttribute("fill", "none"); | |
fore.setAttribute("d", back.getAttribute("d")); | |
g.addEventListener("mousedown", zoomBy(by), false); | |
g.addEventListener("mouseover", zoomOver, false); | |
g.addEventListener("mouseout", zoomOut, false); | |
g.addEventListener("dblclick", cancel, false); | |
return g; | |
} | |
function tick(i) { | |
var x = r * 0.2, | |
y = r * 0.4, | |
g = ms.svg("g"), | |
back = g.appendChild(ms.svg("rect")), | |
chev = g.appendChild(ms.svg("path")); | |
back.setAttribute("pointer-events", "all"); | |
back.setAttribute("fill", "none"); | |
back.setAttribute("x", -y); | |
back.setAttribute("y", -0.75 * y); | |
back.setAttribute("width", 2 * y); | |
back.setAttribute("height", 1.5 * y); | |
chev.setAttribute("class", "chevron"); | |
chev.setAttribute("d", "M" + -x + ",0H" + x); | |
g.addEventListener("mousedown", zoomTo(i), false); | |
g.addEventListener("dblclick", cancel, false); | |
return g; | |
} | |
function move() { | |
var x = r + 6, y = x, size = map.size(); | |
switch (position) { | |
case "top-left": break; | |
case "top-right": x = size.x - x; break; | |
case "bottom-left": y = size.y - y; break; | |
case "bottom-right": x = size.x - x; y = size.y - y; break; | |
} | |
g.setAttribute("transform", "translate(" + x + "," + y + ")"); | |
dragRect.setAttribute("transform", "translate(" + -x + "," + -y + ")"); | |
for (var i in ticks) { | |
if (i == map.zoom()) | |
ticks[i].setAttribute("class", "active"); | |
else | |
ticks[i].removeAttribute("class"); | |
} | |
} | |
function draw() { | |
while (g.lastChild) g.removeChild(g.lastChild); | |
g.appendChild(dragRect); | |
if (panStyle != "none") { | |
panContainer = g.appendChild(ms.svg("g")); | |
panContainer.setAttribute("class", "pan"); | |
var back = panContainer.appendChild(ms.svg("circle")); | |
back.setAttribute("class", "back"); | |
back.setAttribute("r", r); | |
var s = panContainer.appendChild(pan({x: 0, y: -speed})); | |
s.setAttribute("transform", "rotate(0)"); | |
var w = panContainer.appendChild(pan({x: speed, y: 0})); | |
w.setAttribute("transform", "rotate(90)"); | |
var n = panContainer.appendChild(pan({x: 0, y: speed})); | |
n.setAttribute("transform", "rotate(180)"); | |
var e = panContainer.appendChild(pan({x: -speed, y: 0})); | |
e.setAttribute("transform", "rotate(270)"); | |
var fore = panContainer.appendChild(ms.svg("circle")); | |
fore.setAttribute("fill", "none"); | |
fore.setAttribute("class", "fore"); | |
fore.setAttribute("r", r); | |
} else { | |
panContainer = null; | |
} | |
if (zoomStyle != "none") { | |
zoomContainer = g.appendChild(ms.svg("g")); | |
zoomContainer.setAttribute("class", "zoom"); | |
var j = -0.5; | |
if (zoomStyle == "big") { | |
ticks = {}; | |
for (var i = map.zoomRange()[0], j = 0; i <= map.zoomRange()[1]; i++, j++) { | |
(ticks[i] = zoomContainer.appendChild(tick(i))) | |
.setAttribute("transform", "translate(0," + (-(j + 0.75) * r * 0.4) + ")"); | |
} | |
} | |
var p = panStyle == "none" ? 0.4 : 2; | |
zoomContainer.setAttribute("transform", "translate(0," + r * (/^top-/.test(position) ? (p + (j + 0.5) * 0.4) : -p) + ")"); | |
zoomContainer.appendChild(zoom(+1)).setAttribute("transform", "translate(0," + (-(j + 0.5) * r * 0.4) + ")"); | |
zoomContainer.appendChild(zoom(-1)).setAttribute("transform", "scale(-1)"); | |
} else { | |
zoomContainer = null; | |
} | |
move(); | |
} | |
compass.radius = function(x) { | |
if (!arguments.length) return r; | |
r = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.speed = function(x) { | |
if (!arguments.length) return r; | |
speed = x; | |
return compass; | |
}; | |
compass.position = function(x) { | |
if (!arguments.length) return position; | |
position = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.pan = function(x) { | |
if (!arguments.length) return panStyle; | |
panStyle = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.zoom = function(x) { | |
if (!arguments.length) return zoomStyle; | |
zoomStyle = x; | |
if (map) draw(); | |
return compass; | |
}; | |
compass.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
container.removeEventListener("mousedown", mousedown, false); | |
container.removeChild(g); | |
container = null; | |
window.removeEventListener("mousemove", mousemove, false); | |
window.removeEventListener("mouseup", mouseup, false); | |
window = null; | |
map.off("move", move).off("resize", move); | |
} | |
if (map = x) { | |
container = map.svgContainer(); | |
container.appendChild(g); | |
container.addEventListener("mousedown", mousedown, false); | |
window = container.ownerDocument.defaultView; | |
window.addEventListener("mousemove", mousemove, false); | |
window.addEventListener("mouseup", mouseup, false); | |
map.on("move", move).on("resize", move); | |
draw(); | |
} | |
return compass; | |
}; | |
return compass; | |
}; | |
ms.grid = function() { | |
var grid = {}, | |
map, | |
g = ms.svg("g"); | |
g.setAttribute("class", "grid"); | |
function move(e) { | |
var p, | |
line = g.firstChild, | |
size = map.size(), | |
nw = map.pointLocation(zero), | |
se = map.pointLocation(size), | |
step = Math.pow(2, 4 - Math.round(map.zoom())); | |
// Round to step. | |
nw.lat = Math.floor(nw.lat / step) * step; | |
nw.lon = Math.ceil(nw.lon / step) * step; | |
// Longitude ticks. | |
for (var x; (x = map.locationPoint(nw).x) <= size.x; nw.lon += step) { | |
if (!line) line = g.appendChild(ms.svg("line")); | |
line.setAttribute("x1", x); | |
line.setAttribute("x2", x); | |
line.setAttribute("y1", 0); | |
line.setAttribute("y2", size.y); | |
line = line.nextSibling; | |
} | |
// Latitude ticks. | |
for (var y; (y = map.locationPoint(nw).y) <= size.y; nw.lat -= step) { | |
if (!line) line = g.appendChild(ms.svg("line")); | |
line.setAttribute("y1", y); | |
line.setAttribute("y2", y); | |
line.setAttribute("x1", 0); | |
line.setAttribute("x2", size.x); | |
line = line.nextSibling; | |
} | |
// Remove extra ticks. | |
while (line) { | |
var next = line.nextSibling; | |
g.removeChild(line); | |
line = next; | |
} | |
} | |
grid.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
g.parentNode.removeChild(g); | |
map.off("move", move).off("resize", move); | |
} | |
if (map = x) { | |
map.on("move", move).on("resize", move); | |
map.svgContainer().appendChild(g); | |
map.dispatch({type: "move"}); | |
} | |
return grid; | |
}; | |
return grid; | |
}; | |
ms.attribution = function(html) { | |
var attribution = {}, | |
map, | |
container = document.createElement("div"); | |
container.setAttribute("class", "mapsense-attribution"); | |
attribution.container = function() { | |
return container; | |
}; | |
attribution.html = function(x) { | |
if (!arguments.length) return container.innerHTML; | |
container.innerHTML = x; | |
return attribution; | |
}; | |
attribution.map = function(x) { | |
if (!arguments.length) return map; | |
if (map) { | |
if (map === x) { | |
container.parentNode.appendChild(container); | |
return attribution; | |
} | |
container.parentNode.removeChild(container); | |
} | |
map = x; | |
if (map) { | |
map.relativeContainer().appendChild(container); | |
} | |
return attribution; | |
}; | |
return attribution.html(html); | |
}; | |
ms.basemap = function() { | |
var basemap = ms.topoJson(); | |
//var attribution = ms.attribution('<a target="_blank" href="https://developer.mapsense.co/tileViewer/?tileset=mapsense.earth">©Mapsense ©OpenStreetMap</a>'); | |
var 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; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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(); | |
}); | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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