A goofy slippy map of various vector tile data sources. With some "fun" colours, and apologies and respects to Prettymaps. Layers used here:
Tested with Chrome.
A goofy slippy map of various vector tile data sources. With some "fun" colours, and apologies and respects to Prettymaps. Layers used here:
Tested with Chrome.
<!DOCTYPE html> | |
<html lang="en"><head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0"/> | |
<title>Leaflet vector tile map of rivers</title> | |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css" /> | |
<!--[if lte IE 8]> | |
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.ie.css" /> | |
<![endif]--> | |
<script src="http://cdn.leafletjs.com/leaflet-0.5/leaflet.js"></script> | |
<script src="leaflet-hash.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> | |
<script src="TileLayer.d3_geoJSON.js"></script> | |
<style type="text/css"> | |
html, body { height: 100% } | |
#map { min-height: 100%; } | |
body { | |
margin: 0; | |
font-family: Helvetica, Arial, sans-serif; font-size: 12px; | |
overflow: hidden; | |
background-color: #f00; | |
} | |
.leaflet-popup-content-wrapper { | |
-webkit-border-radius: 5px; | |
border-radius: 5px; | |
} | |
path { stroke-linejoin; round; stroke-linecap: round; fill: none} | |
path.river { stroke : #24b; } | |
path.road { stroke: #b42; } | |
path.water { stroke: #bcf; fill: #abf; } | |
path.landuse { stroke: #bb2; fill: #cc2; opacity: 0.5 } | |
img { -webkit-filter: grayscale(100%) brightness(40%) contrast(150%);} | |
</style> | |
</head><body> | |
<div id="map"></div> | |
<script type="text/javascript"> | |
// Construct map, center if no location provided | |
var map = L.map('map', { minZoom: 10, maxZoom: 13 } ); | |
var hash = new L.Hash(map); | |
if (!window.location.hash) { | |
map.setView([39.28, -121.18], 10); | |
} | |
// Make the base map; a raster tile relief map from ESRI | |
var esriRelief = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}' | |
var basemap = L.tileLayer(esriRelief, { | |
attribution: '<a href="http://www.arcgis.com/home/item.html?id=9c5370d0b54f4de1b48a3792d7377ff2">ESRI shaded relief</a>, <a href="http://www.horizon-systems.com/NHDPlus/NHDPlusV2_home.php">NHDPlus v2</a>, OpenStreetMap', | |
maxZoom: 13 | |
}); | |
basemap.addTo(map); | |
// Add a fake GeoJSON line to coerce Leaflet into creating the <svg> tag that d3_geoJson needs | |
new L.geoJson({"type": "LineString","coordinates":[[0,0],[0,0]]}).addTo(map); | |
// Land use areas from OpenStreetMap | |
// Water Areas from OpenStreetMap | |
var landColors = { | |
"farm": "#4a4", | |
"meadow": "#4a4", | |
"scrub": "#4a4", | |
"forest": "#4a4", | |
"farmyard": "#4a4", | |
"farmland": "#4a4", | |
"wood": "#4a4", | |
"park": "#4a4", | |
"cemetery": "#4a4", | |
"golf_course": "#4a4", | |
"grass": "#4a4", | |
"nature_reserve": "#4a4", | |
"pitch": "#4a4", | |
"common": "#4a4", | |
"residential": "#ddd", | |
"industrial": "#b3c", | |
"commercial": "#b3c", | |
"retail": "#b3c", | |
"parking": "#b3c", | |
"quarry": "#b3c", | |
"school": "#b3c", | |
"hospital": "#b3c", | |
"college": "#b3c", | |
"university": "#b3c", | |
} | |
new L.TileLayer.d3_geoJSON("http://tile.openstreetmap.us/vectiles-land-usages/{z}/{x}/{y}.json", { | |
class: "landuse", | |
style: function(d) { | |
var c = landColors[d.properties.kind]; | |
if (!c) { c = "#fff"; } | |
return "fill: " + c; | |
} | |
}).addTo(map); | |
// Rivers from Nelson's vector river tutorial | |
new L.TileLayer.d3_geoJSON("http://somebits.com:8001/rivers/{z}/{x}/{y}.json", { | |
class: "river", | |
style: function(d) { return "stroke-width: " + d.properties.strahler * map.getZoom()/13 + "px"; } | |
}).addTo(map); | |
// Water Areas from OpenStreetMap | |
new L.TileLayer.d3_geoJSON("http://tile.openstreetmap.us/vectiles-water-areas/{z}/{x}/{y}.json", { | |
class: "water", | |
style: function(d) { return ""; } | |
}).addTo(map); | |
// Highways from OpenStreetMap | |
var roadSizes = { | |
"highway": "5px", | |
"major_road": "4px", | |
"minor_road": "2px", | |
"rail": "0px", | |
"path": "1px" | |
}; | |
new L.TileLayer.d3_geoJSON("http://tile.openstreetmap.us/vectiles-highroad/{z}/{x}/{y}.json", { | |
class: "road", | |
style: function(d) { return "stroke-width: " + roadSizes[d.properties.kind]; } | |
}).addTo(map); | |
</script> | |
</body></html> |
(function(window) { | |
var HAS_HASHCHANGE = (function() { | |
var doc_mode = window.documentMode; | |
return ('onhashchange' in window) && | |
(doc_mode === undefined || doc_mode > 7); | |
})(); | |
L.Hash = function(map) { | |
this.onHashChange = L.Util.bind(this.onHashChange, this); | |
if (map) { | |
this.init(map); | |
} | |
}; | |
L.Hash.prototype = { | |
map: null, | |
lastHash: null, | |
parseHash: function(hash) { | |
if(hash.indexOf('#') === 0) { | |
hash = hash.substr(1); | |
} | |
var args = hash.split("/"); | |
if (args.length == 3) { | |
var zoom = parseInt(args[0], 10), | |
lat = parseFloat(args[1]), | |
lon = parseFloat(args[2]); | |
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { | |
return false; | |
} else { | |
return { | |
center: new L.LatLng(lat, lon), | |
zoom: zoom | |
}; | |
} | |
} else { | |
return false; | |
} | |
}, | |
formatHash: function(map) { | |
var center = map.getCenter(), | |
zoom = map.getZoom(), | |
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); | |
return "#" + [zoom, | |
center.lat.toFixed(precision), | |
center.lng.toFixed(precision) | |
].join("/"); | |
}, | |
init: function(map) { | |
this.map = map; | |
// reset the hash | |
this.lastHash = null; | |
this.onHashChange(); | |
if (!this.isListening) { | |
this.startListening(); | |
} | |
}, | |
remove: function() { | |
if (this.changeTimeout) { | |
clearTimeout(this.changeTimeout); | |
} | |
if (this.isListening) { | |
this.stopListening(); | |
} | |
this.map = null; | |
}, | |
onMapMove: function() { | |
// bail if we're moving the map (updating from a hash), | |
// or if the map is not yet loaded | |
if (this.movingMap || !this.map._loaded) { | |
return false; | |
} | |
var hash = this.formatHash(this.map); | |
if (this.lastHash != hash) { | |
location.replace(hash); | |
this.lastHash = hash; | |
} | |
}, | |
movingMap: false, | |
update: function() { | |
var hash = location.hash; | |
if (hash === this.lastHash) { | |
return; | |
} | |
var parsed = this.parseHash(hash); | |
if (parsed) { | |
this.movingMap = true; | |
this.map.setView(parsed.center, parsed.zoom); | |
this.movingMap = false; | |
} else { | |
this.onMapMove(this.map); | |
} | |
}, | |
// defer hash change updates every 100ms | |
changeDefer: 100, | |
changeTimeout: null, | |
onHashChange: function() { | |
// throttle calls to update() so that they only happen every | |
// `changeDefer` ms | |
if (!this.changeTimeout) { | |
var that = this; | |
this.changeTimeout = setTimeout(function() { | |
that.update(); | |
that.changeTimeout = null; | |
}, this.changeDefer); | |
} | |
}, | |
isListening: false, | |
hashChangeInterval: null, | |
startListening: function() { | |
this.map.on("moveend", this.onMapMove, this); | |
if (HAS_HASHCHANGE) { | |
L.DomEvent.addListener(window, "hashchange", this.onHashChange); | |
} else { | |
clearInterval(this.hashChangeInterval); | |
this.hashChangeInterval = setInterval(this.onHashChange, 50); | |
} | |
this.isListening = true; | |
}, | |
stopListening: function() { | |
this.map.off("moveend", this.onMapMove, this); | |
if (HAS_HASHCHANGE) { | |
L.DomEvent.removeListener(window, "hashchange", this.onHashChange); | |
} else { | |
clearInterval(this.hashChangeInterval); | |
} | |
this.isListening = false; | |
} | |
}; | |
L.hash = function(map) { | |
return new L.Hash(map); | |
}; | |
L.Map.prototype.addHash = function() { | |
this._hash = L.hash(this); | |
}; | |
L.Map.prototype.removeHash = function() { | |
this._hash.remove(); | |
}; | |
})(window); |
/* Experimental vector tile layer for Leaflet | |
* Uses D3 to render GeoJSON; faster than Leaflet's native. | |
* Originally by Ziggy Jonsson: http://bl.ocks.org/ZJONSSON/5602552 | |
* Reworked by Nelson Minar: http://bl.ocks.org/NelsonMinar/5624141 | |
* | |
* Todo: | |
* Make this work even if <svg> isn't in the DOM yet | |
* Make this work for tile types that aren't FeatureCollection | |
* Match D3 idioms for .classed(), .style(), etc | |
* Work on allowing feature popups, etc. | |
*/ | |
L.TileLayer.d3_geoJSON = L.TileLayer.extend({ | |
onAdd : function(map) { | |
L.TileLayer.prototype.onAdd.call(this,map); | |
this._path = d3.geo.path().projection(function(d) { | |
var point = map.latLngToLayerPoint(new L.LatLng(d[1],d[0])); | |
return [point.x,point.y]; | |
}); | |
this.on("tileunload",function(d) { | |
if (d.tile.xhr) d.tile.xhr.abort(); | |
if (d.tile.nodes) d.tile.nodes.remove(); | |
d.tile.nodes = null; | |
d.tile.xhr = null; | |
}); | |
}, | |
_loadTile : function(tile,tilePoint) { | |
var self = this; | |
this._adjustTilePoint(tilePoint); | |
if (!tile.nodes && !tile.xhr) { | |
tile.xhr = d3.json(this.getTileUrl(tilePoint),function(geoJson) { | |
tile.xhr = null; | |
tile.nodes = d3.select(map._container).select("svg").append("g"); | |
tile.nodes.selectAll("path") | |
.data(geoJson.features).enter() | |
.append("path") | |
.attr("d", self._path) | |
.attr("class", self.options.class) | |
.attr("style", self.options.style); | |
}); | |
} | |
} | |
}); |