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);
}
});
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.
Related
I'm trying to draw choropleth map of Seoul but it just gives me only a black rectangle.
I thought there might be a problem with projection.scale but I couldn't figure it out.
I used scale.fitSize() function to handle it. but my d3 is older version that not available of fitSize().
my code is here:
var width = 600, height = 700;
var svg = d3.select('#chart').append('svg')
.attr('width',width)
.attr('height',height);
var projection = d3.geo.mercator()
.center([128,36])
.scale(5000)
.translate([width/2, height/2]);
var path = d3.geo.path()
.projection(projection);
d3.json('Seoulmap.json',function(error,data) {
var features = topojson.feature(data, data.objects['Seoulmap']).features;
svg.selectAll('path')
.data(features)
.enter().append('path')
.attr('class','name')
.attr('d',path)
.attr('id',function(d) { return d.properties.ADM_DR_NM; });
});
How my code is rendering black rectangle:
I am working on a project where I am trying to visualize data from a database onto a leaflet map. The data contains long/lat coodrinates, but when I'm trying to draw them onto the map, the location is all wrong or not showing at all.
I've read that the lat / long coordinates are not the same coordinates that are being used on the leaflet map, so i'm trying to translate them but with no luck.
The datebase has this structure (.csv file):
datetime,city,state,country,shape,durationSeconds,durationHours,comments,date_posted,latitude,longitude
10/10/1949 20:30,san marcos,tx,us,cylinder,2700,45 minutes,"This event took place in early fall around 1949-50. It occurred after a Boy Scout meeting in the Baptist Church. The Baptist Church sit",4/27/2004,29.8830556,-97.9411111
10/10/1949 21:00,lackland afb,tx,,light,7200,1-2 hrs,"1949 Lackland AFB, TX. Lights racing across the sky & making 90 degree turns on a dime.",12/16/2005,29.38421,-98.581082
and the code so far looks like this:
var map = L.map('mapid').setView([10, 15], 2.2); L.tileLayer('https://api.mapbox.com/styles/v1/josecoto/civ8gwgk3000a2ipdgnsscnai/'
+'tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1Ijoiam9zZWNvdG8iLCJhIjoiY2l2OGZxZWNuMDAxODJ6cGdhcGFuN2IyaCJ9.7szLs0lc_2EjX6g21HI_Kg', {
maxZoom: 18,
id: 'mapbox.streets',
accessToken: 'your.mapbox.access.token'
}).addTo(map);
var w = $("#mapid").width();
var h = $("#mapid").height();
var projection = d3.geoMercator()
.scale(w / 2 / Math.PI)
.translate([w / 2, h / 2])
function latLong(x,y)
{
//console.log(x + ' and ' + y);
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
return point;
}
var svg_map = d3.select(map.getPanes().overlayPane)
.append("svg")
.attr("width", w)
.attr("height", h);
svg_map.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
var coords = projection([d.longitude, d.latitude]);
return coords[0];
//var coords = latLong(d.longitude, d.latitude);
//return coords.x;
})
.attr("cy", function(d) {
var coords = projection([d.longitude, d.latitude]);
return coords[1];
//var coords = latLong(d.longitude, d.latitude);
//return coords.y;
})
.attr("r", function(d) {
return 2;
})
When I'm using the geoMercator() function, I can see the dots displayed on the map, however they are being projected onto the map wrong (because they are being bent from the function or something). And when I'm trying to use the latLong() function, they are not being displayed at all (but no errors in debugger).
I can add that I am getting the values from the database correctly, but the problem is in the vizualisation. Can you guys spot anything?
Thank you in advance!
EDIT: Here's an image of how it looks when I'm using the geoMercator:
I am drawing a map of the United States,Costa Rica and Canada. And I would like the maps to adapt to the size of the div#statesvg
<div id="statesvg" style="width:100%; height:100%"></div>
the size of div#statesvg is dynamic.
I only want by default that the maps fits exactly to the div that contains it.
I'm trying to center each map, but it's not centered. I would like to know if there is any mathematical formula or something to scale the map until it fully occupies the svg container.
if(json=="usa.json"){
// D3 Projection
var projection = d3.geo.albersUsa()
.translate([width/2, height/2])
.scale((height*1.25));
}
if(json=="canada.json"){
//canada lat long 54.6965251,-113.7266353
var projection = d3.geo.mercator()
.center([-113.7266353,54.6965251 ])
.translate([width/2, height/2])
.scale((height*1.25));
}
if(json=="costarica.json"){
//costa rica lat long
var projection = d3.geo.mercator()
.center([-87.0531006,8.351569 ])
.translate([width/2, height/2])
.scale((height*1.25));
}
// Define path generator
var path = d3.geo.path() // path generator that will convert GeoJSON to SVG paths
.projection(projection); // tell path generator to use albersUsa projection
this is my actual problem for each map
thanks!
this is my code:
http://plnkr.co/edit/lJgx0fbLcEh7e4W3j6XD?p=preview
There is this nice gist from nrabinowitz, which provides a function which scales and translate a projection to fit a given box.
It goes through each of the geodata points (data parameter), projects it (projection parameter), and incrementally update the necessary scale and translation to fit all points in the container (box parameter) while maximizing the scale:
function fitProjection(projection, data, box, center) {
...
return projection.scale(scale).translate([transX, transY])
}
I've adapted part of your code (the Canada map) to use it:
d3.json("canada.json", function(data) {
var projection =
fitProjection(d3.geo.mercator(), data, [[0, 0], [width, height]], true)
var path = d3.geo.path().projection(projection);
d3.select("#statesvg svg").remove();
var svg = d3.select("#statesvg").append("svg")
.attr("width", width+"px")
.attr("height", height+"px");
svg.selectAll("path")
.data(data.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.style("stroke-width", "1")
.style("fill", function(d) { return "rgb(213,222,217)"; });
});
As commented by the author, it seems to only work for Mercator projections.
i want to draw on map base on longitude and latitude in csv file called tree.csv on a map that i using an image .
My csv file include many lines ,so i will just put some lines here
Longitude Latitude
37.7295482207565 122.392689419827
37.8030467266869 122.425063628702
......
Here is my code
d3.csv("/trees.csv", function(data) {
dataset=data.map(function(d) { return [+d["Longitude"],+d["Latitude"] ];});
console.log(data)
var width = 750,
height = width;
// Set up projection that map is using
var projection = d3.geo.mercator()
.center([-122.433701, 37.767683])
.scale(225000)
.translate([width / 2, height / 2]);
var path=d3.geo.path().projection(projection);
var svgContainer=d3.select("body").append("svg")
.attr("width",width)
.attr("height",height);
svgContainer.append("image")
.attr("width", width)
.attr("height", height)
.attr("xlink:href", "/Ilu.svg");
var trees=svgContainer.selectAll("circles")
.data(data).enter()
.append("circles")
var treesAttributes=trees
.attr("cx",function(d) { return projection(d["Longitude"])[0];})
.attr("cy",function(d) { return projection(d["Latitude"])[1];})
.attr("r","100px")
.style("fill","red");
I can see my map but i cant see any points on my map . When i inspect the web. i see that cx is Nan number ,and cy is same number. I think maybe my array havent been read yet. But i am not sure about the problems. I have been stucked. Can you guys solve me the problem ? Thank you
Your problem lies in that you aren't providing coordinates to be projected.
A d3 geoProjection takes a longitude latitude pair and projects it to an x,y svg coordinate (a projection returns a coordinate as: [x,y], which is why you use this form in your code: projection(coord)[0] to get the cx value). You are seeking to project only a longitude, and then only a latitude:
.attr("cx",function(d) { return projection(d["Longitude"])[0];})
.attr("cy",function(d) { return projection(d["Latitude"])[1];})
In this case, projection won't return an svg coordinate as you aren't providing a geographical coordinate to project. You need to project both longitude and latitude, becuase x and y values produced in a projection are usually (not always) co-dependent - in any conical projection for example, the output y (or x) value is dependent on both latitude and longitude. Further, as projection() returns [x,y], it requires both longitude and latitude for every projection.
Instead try:
.attr("cx",function(d) { return projection([d["Longitude"],d["Latitude"]])[0];})
.attr("cy",function(d) { return projection([d["Longitude"],d["Latitude"]])[1];})
Remeber that d3 geoprojections expect the form: projection([longitude, latitude]), changing the order of longitude and latitude will produce unexpected results.
var data = [
{longitude:1,latitude:1},
{longitude:-1,latitude:1},
{longitude:1,latitude:-1},
{longitude:-1,latitude:-1}
]
var svg = d3.select("body")
.append("svg")
.attr("width",200)
.attr("height",200);
var projection = d3.geoMercator()
.translate([100,100]);
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx",function(d) { return projection([d.longitude,d.latitude])[0];
})
.attr("cy",function(d) { return projection([d["longitude"],d["latitude"]])[1];
})
.attr("r",2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
I have brought in the zoom functionality on mouse wheel to my world map in d3.
I am using the below code for that,
.on("wheel.zoom",function(){
var currScale = projection.scale();
var newScale = currScale - 2*event.deltaY;
var currTranslate = projection.translate();
var coords = projection.invert([event.offsetX, event.offsetY]);
projection.scale(newScale);
var newPos = projection(coords);
projection.translate([currTranslate[0] + (event.offsetX - newPos[0]), currTranslate[1] + (event.offsetY - newPos[1])]);
g.selectAll("path").attr("d", path);
})
.call(d3.drag().on("drag", function(){
var currTranslate = projection.translate();
projection.translate([currTranslate[0] + d3.event.dx,
currTranslate[1] + d3.event.dy]);
g.selectAll("path").attr("d", path);
}))
But my problem is that the bubbles placed on map is not getting zoomed as per the zoomin of map also the location of bubbles getting misplaced.
Below is the link for my current working code.
https://plnkr.co/edit/jHJ4R1YhI9yPLusUMLh0?p=preview
If you update your projection, you need to update all your features to reflect the new projection. You've done this with your path (this in relation to your drag event, as I was unable to scroll wheel zoom, but the principle is the same):
// update projection
var currTranslate = projection.translate();
projection.translate([currTranslate[0] + d3.event.dx, currTranslate[1] + d3.event.dy]);
// update paths
g.selectAll("path").attr("d", path);
But, you did not update the circles, as circles are not paths.
You can simply re-apply how you positioned the circles in the first place, afterall you are doing the same thing only with an updated projection:
// update projection
var currTranslate = projection.translate();
projection.translate([currTranslate[0] + d3.event.dx, currTranslate[1] + d3.event.dy]);
// update paths coordinates
g.selectAll("path").attr("d", path);
// update circle coordinates
svg.selectAll(".city-circle")
.attr("cx",function(d){
var coords = projection([d.Attribute_Longitude,d.Attribute_Latitude]);
return coords[0];
})
.attr("cy",function(d){
var coords = projection([d.Attribute_Longitude,d.Attribute_Latitude]);
return coords[1];
})
Here's an updated plunker.