Skip to content

Instantly share code, notes, and snippets.

@NelsonMinar
Last active December 18, 2015 13:18
Show Gist options
  • Save NelsonMinar/5788481 to your computer and use it in GitHub Desktop.
Save NelsonMinar/5788481 to your computer and use it in GitHub Desktop.
Rivers: search + Stamen terrain
.leaflet-control-geocoder a {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-control-geocoder {
box-shadow: 0 1px 7px #999;
background: #f8f8f9;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
border-radius: 8px;
}
.leaflet-control-geocoder a {
background-image: url(images/geocoder.png);
width: 36px;
height: 36px;
}
.leaflet-touch .leaflet-control-geocoder a {
width: 44px;
height: 44px;
}
.leaflet-control-geocoder .leaflet-control-geocoder-form,
.leaflet-control-geocoder-expanded .leaflet-control-geocoder-toggle {
display: none;
}
.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form {
display: block;
position: relative;
}
.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form {
padding: 5px;
}
L.Control.NominatimGeocoder = L.Control.extend({
options: {
collapsed: true,
position: 'topright',
text: 'Locate',
callback: function (results) {
// Check if no results are returned
if (results.length >= 1) {
// Just take the first place
var bbox = results[0].boundingbox,
southWest = new L.LatLng(bbox[0], bbox[2]),
northEast = new L.LatLng(bbox[1], bbox[3]),
bounds = new L.LatLngBounds(southWest, northEast);
// Center on it
this._map.fitBounds(bounds);
}
// TODO Indicate lack of results to user
}
},
_callbackId: 0,
initialize: function (options) {
L.Util.setOptions(this, options);
},
onAdd: function (map) {
this._map = map;
var className = 'leaflet-control-geocoder',
container = this._container = L.DomUtil.create('div', className);
L.DomEvent.disableClickPropagation(container);
var form = this._form = L.DomUtil.create('form', className + '-form');
var input = this._input = document.createElement('input');
input.type = "text";
var submit = document.createElement('button');
submit.type = "submit";
submit.innerHTML = this.options.text;
form.appendChild(input);
form.appendChild(submit);
L.DomEvent.addListener(form, 'submit', this._geocode, this);
if (this.options.collapsed) {
L.DomEvent.addListener(container, 'mouseover', this._expand, this);
L.DomEvent.addListener(container, 'mouseout', this._collapse, this);
var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
link.href = '#';
link.title = 'Nominatim Geocoder';
L.DomEvent.addListener(link, L.Browser.touch ? 'click' : 'focus', this._expand, this);
this._map.on('movestart', this._collapse, this);
} else {
this._expand();
}
container.appendChild(form);
return container;
},
_geocode : function (event) {
L.DomEvent.preventDefault(event);
this._callbackId = "_l_nominatimgeocoder_" + (this._callbackId++);
window[this._callbackId] = L.Util.bind(this.options.callback, this);
var params = {
q: this._input.value,
format: "json",
// We only use the first result currently
limit: 1,
json_callback : this._callbackId
},
url = "http://open.mapquestapi.com/nominatim/v1/search" + L.Util.getParamString(params),
script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
script.id = this._callbackId;
document.getElementsByTagName("head")[0].appendChild(script);
},
_expand: function () {
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
},
_collapse: function () {
this._container.className = this._container.className.replace(' leaflet-control-geocoder-expanded', '');
}
});
<!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" />
<link rel="stylesheet" href="Control.NominatimGeocoder.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.5/leaflet.js"></script>
<script src="leaflet-hash.js"></script>
<script src="Control.NominatimGeocoder.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 Neue, 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: #29439c; }
</style>
</head><body>
<div id="map"></div>
<script type="text/javascript">
// Construct map, center if no location provided
var map = L.map('map', { maxZoom: 18 } );
var hash = new L.Hash(map);
if (!window.location.hash) {
map.setView([37.958, -120.976], 8);
}
// Add Nominatim search
var geocoder = new L.Control.NominatimGeocoder({"collapsed": false});
map.addControl(geocoder);
// Fake GeoJSON line to make Leaflet create the <svg> tag that d3_geoJson needs
new L.geoJson({"type": "LineString","coordinates":[[0,0],[0,0]]}).addTo(map);
// Make the base map
var baseUrl = 'http://{s}.tile.stamen.com/terrain/{z}/{x}/{y}.png';
var basemap = L.tileLayer(baseUrl, {
attribution: '<a href="http://maps.stamen.com/">Stamen base map</a>, <a href="http://www.horizon-systems.com/NHDPlus/NHDPlusV2_home.php">NHDPlus v2</a>, <a href="http://www.mapquest.com/" target="_blank">MapQuest Nominatim</a>',
maxZoom: 20
});
basemap.addTo(map);
// Style the river lines; width depends on its Strahler number
function riverStyle(feature) {
return "stroke-width: " + feature.properties.strahler * map.getZoom()/13 + "px;";
}
// Make the river overlay layer, vector tiles from our TileStache/Gunicorn server
var geojsonURL = "http://www.somebits.com:8001/rivers/{z}/{x}/{y}.json";
var riverLayer = new L.TileLayer.d3_geoJSON(geojsonURL, {
class: "river",
style: riverStyle,
});
map.addLayer(riverLayer);
</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
*
* While working (and fast!) this plugin requires work to work like
* a typical Leaflet layer. Todo:
* Make this work even if <svg> isn't in the DOM yet;
* For now, add a fake GeoJSON layer to your map before one of these
* new L.geoJson({"type":"LineString","coordinates":[[0,0],[0,0]]}).addTo(map);
* Make this work for tile types that aren't FeatureCollection
* Match D3 idioms for .classed(), .style(), etc? Or match the Leaflet API?
* 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);
});
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment