Overlay d3 paths onto Google Maps?

2020-02-02 04:28发布

问题:

I'm trying to overlay a map onto Google Maps using d3.geo and GeoJson. I've managed to force d3 to use the Google Map's projection for drawing the paths, which was surprisingly easy. Here's what I have so far:

http://www.caudillweb.com/temp/d3_choropleth.html

This works well as I zoom in and out:

But when I pan, the SVG overlay moves too, and since its size is fixed, the shapes get truncated:

Has anyone gotten something like this to work? Any ideas where I could go from here? The above example is a single self-contained HTML file if anyone wants to play with it.

回答1:

In this example the width and height of the svg are set to 8000 x 8000, which seemed to be OK up to about zoom level 9-10. The top and left are set to -4000 x -4000 to center it. Finally, the projection is shifted/offset so that it draws in the center of the svg:

Enlarge and center the svg:

.SvgOverlay svg {
    position: absolute;
    top: -4000px;
    left: -4000px;
    width: 8000px;
    height: 8000px;        
}

Offset the projection:

return [pixelCoordinates.x + 4000, pixelCoordinates.y + 4000];


回答2:

One option is to set the initial map size to something very large, say, 1000 x 1000 pixels or larger. This will force the SVG overlay to render within the viewable area (which seems to be the constraint with Firefox). I realize that this may screw with the design of your site, so you might want to consider wrapping the map with a div to hide the overflow.

#map-wrap {
    width: 500px;
    height: 500px;
    overflow: hidden;
}

#map {
    width: 1000px; /* just big enough to show the whole thing at zoom #6 */
    height: 1000px;
}

Once the map renders, you can re-size the map to the desired size of 500 x 500 and re-center the map. This can be done in the bounds_changed event:

google.maps.event.addListener(map, 'bounds_changed', function() {
    $map.css({ height: '500px', width: '500px' }); // back to desired dims
    google.maps.event.trigger(map, 'resize'); // trigger a 'refresh'
    map.setCenter(new google.maps.LatLng(-18.2, 35.1)); // re-center map
    google.maps.event.clearListeners(map, 'bounds_changed'); // don't do this again
});

Here is a working example (Tested in Firefox and Chrome).

Some caveats:

  1. The farther you want to allow people to zoom in, the larger you will need to render the initial "temporary" map. For instance, if you want to allow for zoom level 7, you may need to make the initial map 2000 x 2000 to compensate. Obviously there are performance concerns when rendering a map that big. As disperse pointed out, it may be wise to restrict the zoom levels.
  2. Due to the last-minute re-sizing, there will be a flicker as the map readjusts. You may want to consider hiding/showing the map using jQuery, or perhaps placing a white overlay on top of everything until the map has finished re-sizing.


回答3:

I was able to fix the issue at that zoom level by making the svg bigger, adding some padding, and giving a negative top and left position like so:

    .SvgOverlay, .SvgOverlay svg {
        position: absolute;
        top: -250px;
        left: -250px;
        padding: 500px;            
    }

    var svg = layer.append("svg")
          .attr("width", 1000)
          .attr("height", 1000)

If you want to allow your user to zoom in further you'll have to increase all of these numbers. Increasing them by a factor of 10, for instance, seems to fix the issue for all zoom levels. You may, however, want to restrict the user's ability to zoom as the usefulness of the svg overlay is reduced as you zoom in further.

Here is a Fiddle demonstrating the idea. http://jsfiddle.net/VHWqq/7/

Edit: As Herb points out, this approach doesn't work on Firefox.