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