Skip to content

Instantly share code, notes, and snippets.

@NelsonMinar
Last active December 18, 2015 13:18

Revisions

  1. NelsonMinar revised this gist Jun 15, 2013. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -41,7 +41,7 @@
    <script type="text/javascript">

    // Construct map, center if no location provided
    var map = L.map('map', { maxZoom: 15 } );
    var map = L.map('map', { maxZoom: 18 } );
    var hash = new L.Hash(map);
    if (!window.location.hash) {
    map.setView([37.958, -120.976], 8);
    @@ -57,7 +57,7 @@
    // 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>',
    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);
  2. NelsonMinar revised this gist Jun 15, 2013. 2 changed files with 8 additions and 0 deletions.
    8 changes: 8 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    ## River map with search and Stamen terrain

    Data from:

    * [My vector river tiles](https://github.com/NelsonMinar/vector-river-map)
    * [Stamen terrain basemap](http://maps.stamen.com/)
    * [MapQuest Nominatim](http://wiki.openstreetmap.org/wiki/Nominatim) search

    Binary file added thumbnail.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.
  3. NelsonMinar revised this gist Jun 15, 2013. 5 changed files with 419 additions and 1 deletion.
    38 changes: 38 additions & 0 deletions Control.NominatimGeocoder.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    .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;
    }
    96 changes: 96 additions & 0 deletions Control.NominatimGeocoder.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,96 @@
    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', '');
    }
    });
    46 changes: 46 additions & 0 deletions TileLayer.d3_geoJSON.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,46 @@
    /* 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);
    });
    }
    }
    });
    81 changes: 80 additions & 1 deletion index.html
    Original file line number Diff line number Diff line change
    @@ -1 +1,80 @@
    .
    <!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: 15 } );
    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>',
    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>
    159 changes: 159 additions & 0 deletions leaflet-hash.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,159 @@
    (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);
  4. NelsonMinar created this gist Jun 15, 2013.
    1 change: 1 addition & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    .