0

I'm using Leaflet to create a map with an added D3 layer on top. I want to automatically scale and zoom to the overlay layer, similar to the way you can automatically fit geo objects within their container in pure D3 (see example).

In making Leaflet and D3 play nicely I have to use a custom geo transformation per this example:

function projectPoint(x, y) {
  var point = map.latLngToLayerPoint(new L.LatLng(y, x));
  this.stream.point(point.x, point.y);
}

var transform = d3.geo.transform({point: projectPoint}),
    path = d3.geo.path().projection(transform);

This makes projecting D3 onto a Leaflet map effortless, but I'm left without any clue as to how to determine latitude/longitude for my layer map. I need these coordinates in order to set the center, then I'd also need to set the zoom level.

How can I set automatically setView and setZoom in Leaflet to fit a D3 overlay layer?

Here is my implementation:

var map = new L.Map("map", {
    // Initialize map with arbitrary center/zoom
    center: [37.8, -96.9], 
    zoom: 4
})

var layer = map
    .addLayer(new L.TileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"));

var figure = d3.select('figure');
var width = figure.node().clientWidth;
var height = figure.node().clientHeight;

var svg = d3.select(map.getPanes().overlayPane)
    .append("svg")
    .style('width', width)
    .style('height', height);

var g = svg.append("g").attr("class", "leaflet-zoom-hide");

function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}

var transform = d3.geo.transform({ point: projectPoint });
var path = d3.geo.path().projection(transform);

d3.json('conway-ar.json', function(error, collection) {
    if (error) console.warn(error);

    var city = g.append('path')
        .datum(collection.city.geometry)
        .attr('fill', 'none')
        .attr('stroke', 'blue')
        .attr('d', path);

    // Update center/zoom based on location of "city"
    // map.setView([someLat, someLng]);
    // map.setZoom(someZoomLevel);

    map.on('viewreset', update);
    update();

    function update() {
        city.attr('d', path);
    }

});

1 Answer 1

2

I was able to implement a solution using Leaflet.D3SvgOverlay, a library for using D3 with Leaflet that automates the geo transforms.

First I recorded the bounds of the rendered path with proj.pathFromGeojson.bounds(d). This library had a handy method that converted layer points to latitude/longitude, proj.layerPointToLatLng. I was then able to use D3's map.fitBounds to simultaneously adjust the center/zoom based on the recorded boundaries. See the following code:

var bounds = [];
var city = sel.append('path')
  .datum(cityGeo)
  .attr('d', function(d) {
    bounds = proj.pathFromGeojson.bounds(d);
    return proj.pathFromGeojson(d); 
  });

var b = [proj.layerPointToLatLng(bounds[0]),
         proj.layerPointToLatLng(bounds[1])];
map.fitBounds(b);

The full implementation of this can be seen in my bl.ock.

Not the answer you're looking for? Browse other questions tagged or ask your own question.