I've downloaded this map of Great Britain and Ireland from Highcharts and converted from GeoJSON to TopoJSON format (topojson -o input output) but it just renders as a black box. I'm following the famous 'Let's make a map' tutorial:
var container = d3.select( "#container" );
var margin = 20,
width = parseInt(container.style( "width" )),
height = parseInt(container.style( "height" ));
var projection = d3.geo.mercator()
.scale(50);
var path = d3.geo.path()
.projection(projection);
var svg = container.append("svg")
.attr("width", width)
.attr("height", height);
d3.json("http://www.lenart.pl/assets/codepen/gb_all_ireland_topo.json", function(error, gb) {
svg.append("path")
.datum(topojson.feature(gb, gb.objects.gb_all_ireland_geo))
.attr("d", path);
});
http://codepen.io/znak/pen/rVYbNB
I've previewed my TopoJSON file with http://jsoneditoronline.org and found the relevant object containing geometries (I think) but it doesn't work.
My ultimate goal is to visualise an interactive (administrative) map of GB & I. Thanks.
I believe the first spot of bother is the download from Highcharts--it seems to be in a projection that is not wgs84. So the coordinates given are not latitudes and longitudes. If you look at the beginning of highcharts.com/mapdata/custom/gb-all-ireland.geo.json you'll see a few "crs" keys, and mention of "EPSG:7405".
You can convert to wgs84 several ways, including:
ogr2ogr -f geojson eire_4326.geojson gb-all-ireland.geo.json -t_srs epsg:4326 -s_srs epsg:7405
You could then convert to topojson and pick up where you left off.
It's also important to realize that small changes in the scale and center values of the d3 projections can have big impacts.
If you're not tied to Highcharts, it's probably better to get data that's already in wgs84, like http://www.naturalearthdata.com/ or even use a more comprehensive library and data like mapsense https://developer.mapsense.co/tileViewer/. (Disclosure: I work at mapsense.)
Related
I’m building a (d3 v4) cartographic visualization which allows the user to switch between many datasets (json files) and two different regions (administrative units of a country and smaller administrative units into its capital city). Actually the switch from one to another dataset on the initial country level works well, through buttons and jquery.
Problem: it’s a bit less convincing when switching to a map/dataset about the capital city, as the projection is initially set for the whole country and consequently the user has to zoom many times to visualize properly the map of the capital city. I would like to change the values of .scale and .center when calling the projection but after several trials I haven’t found how to do it.
As I only have two different regions to show, my intuition was to set first values of scale and center and to change them to other values (I know the values of .scale and .center I would like to use in both cases) when the user switches to a map of the capital city through a function. Is there any possibility to switch easily these values? Do you have any suggestion to solve this problem?
As I load the json file path into a function when the user clicks on the button to switch to another dataset, I was trying to load the value of scale the same way but I’m probably doing wrong. It seems that the part of the code about the projection can't be put in a function?
Thanks for your help!
Small part of my code:
var width = 1100, height = 770;
var projection = d3.geoConicConformal()
.scale(19000) // value I would like to which when the region changes
.center([4.45, 50.53]) // value I would like to which when the region changes
.translate([width/2,height/2]);
var svg = d3.select( "#mapcontainer" )
.append( "svg" )
.attr("width", width)
.attr("height", height)
.style("border", "solid 1px black");
var path = d3.geoPath()
.projection(projection);
var color, jsonfile, legendtext;
function load (jsonfile, legendtext, color) {
d3.selectAll(".currentmap").remove() ;
d3.json(jsonfile, function(error, belgique) {
g.selectAll("path")
.data(belgique.features)
.enter()
.append("path")
.attr("d", path)
.style("stroke", "#fff")
.attr( "class", "currentmap")
.style("fill", function(d) {
var value = d.properties.DATA;
if (value) {return color(value);}
else {return "rgb(250,110,110)"}
});
})
};
//one of the following function for each map
function BGQprovinces() {
jsonfile = "ATLAS/NewGeoJson/bgq-data1-provinces.json";
legendText [= …];
color = d3.scaleOrdinal()
.domain( […])
.range([…]);
load(jsonfile, legendtext, color) ;
};
;
There area few approaches to accomplish this.
fitSize and fitExtent
One is to modify the projection scale and translate as opposed to scale and center. This is nearly the same operation, but translate pans the projected plane and center will pan the unprojected plane. To do so you need to use projection.fitSize([width,height],geojsonObject), or projection.fitExtent([[x0,y0],[x1,y1]],geojsonObject). The latter will allow margins of say, the first coordinate provided is the top left and the second coordinate provided is the bottom right of a bounding box in which the feature will be constrained.
d3.json(jsonfile, function(error, belgique) {
projection.fitSize([width,height], belgique);
// now draw as you would:
d3.selectAll(".currentmap").remove() ;
g.selectAll("path")
.data(belgique.features)
.enter()
.append("path")
.attr("d", path)
...
Note that for showing all of a country you need to have a feature that shows the whole country or a feature collection that shows all the parts of a country. You cannot use an array with fitSize or fitExtent, if you have an array of features, you can create a feature collection by using:
var featureCollection = {"type":"featureCollection","features":featureArray}
For your case, I'd suggest using fitSize or fitExtent.
centroid
If you really wanted to modify the center attribute as opposed to translate, or perhaps you want to change the rotation (a more likely outcome for conic conformals in many parts of the world, Belgium should be fine), then you need the geographic coordinates of the center. One way of a handful to do this is to get the centroid of a feature from path.geoCentroid:
var centroid = path.geoCentroid(geojsonObject);
Then use that to set the projection parameters to rotate:
projection.rotate([ -centroid[0],-centroid[1] ])
projection.center([0,0])
or to center:
projection.rotate([0,0])
projection.center(centroid)
Or a combination of both (depending on map projection type). Now you can apply fitSize or fitExtent, the feature is in the middle already, but now we can set the scale. The reason I suggest this as a potential answer is because not all projections, concic projections in particular, will give desired results by modifying only scale along with translate and/or center.
Of course for conic projections, you may need to find a way to set the parallels as well, but I'll leave that for another answer if it ever comes up.
UPDATE: solved, kind of. I spent some time learning ogr2ogr and topojson and made a topojson that works beautifully in D3. I'm not sure why the file spit out by mapshaper.org turned into shards in my hands; on their site, the file displays fine.
ORIGINAL POST:
There may be an answer out there, but how do you ask google "why does my d3 map look like zig zags and shards?" FWIW, "search by image" turned up nothing. ;)
I suspect it's something with wrong projection or wrong conversion from shapefiles? I've been converting via mapshaper.org while I'm learning ogr2ogr and topojson myself.
This should be a map of two Georgia counties, an area maybe 100 miles across.
The shapefile came from U.S. Census Tiger.
UPDATE: Tiger's .xml seems to suggest the projection is WGS_1984_Web_Mercator_Auxiliary_Sphere
I got another shapefile of the same area from our state mapping agency and it mapped perfectly.
Any ideas?
d3.json("050_00.json", function(error, counties) {
if (error) return console.error(error);
console.log("ok counties:", counties)
var projection = d3.geo.mercator()
.scale(scale)
.rotate([84.2, -33.9, 0])
var path = d3.geo.path()
.projection(projection);
svg.selectAll("#counties")
.data(topojson.feature(counties, counties.objects['050_00']).features)
.enter().append("path")
.attr("id", "counties")
.attr("d", path)
.attr("opacity", 0.5);
});
I have a Dimple.JS scatter plot with a time-based (in years) X-axis. I'd like (in a similar manner to this D3 question) to be able to shade in an arbitrary area (ideally the start and end positions wouldn't necessarily be data points in the series).
Is there an existing function that will let me supply a year and give me the X co-ordinate the correct position on the scale in the SVG, which I can then use the construct my rectangle (I tried to look at the source code to figure out how dimple does it's positioning...)?
Alternatively, if it's more practical to use points already plotted on the chart, what's the correct way to use d3.select with dimple to access a specific one? My series has a date field (dd/mm/yyyy) so I have SVG elements like this:
<circle id="All_Wed Mar 18 1931 00:00:00 GMT+0000 (GMT)__" class="series0 bubble All Wed_Mar_18_1931_00:00:00_GMT+0000_(GMT) " cx="465.0000000006503" cy="362.1714285714286" r="2" opacity="0.8" fill="#e90e0e" stroke="#c20b0b"></circle>
… my guess was I should use mySeries.shapes.select(id) to access that, but for:
mySeries.shapes.select("#All_Wed Mar 18 1931 00:00:00 GMT+0000 (GMT)__");
or (if I escape it, unless there's a silly syntax error):
mySeries.shapes.select("#All_Wed Mar\ 18\ 1931\ 00:00:00\ GMT+0000\ (GMT)__");
I get "Not a valid selector".
(Thanks)
You need to use a non-public method of the axes to do this, so it may not work this way in future versions (>1.1.5) however between you and me, I don't think the scale method of the axis is going to be disappearing any time soon.
The _scale method is the raw d3 scale method added once the draw method of the chart is called so it can convert the values for you. I've created a fiddle to illustrate the solution. This will need a little tweaking if you are dealing with negative values or log axes:
// Draw a simple chart
var svg = dimple.newSvg("body", 800, 600);
var data = [
{ "a":300, "b":2000, "c":"a" },
{ "a":400, "b":3000, "c":"b" },
{ "a":340, "b":2200, "c":"c" },
{ "a":300, "b":5000, "c":"d" }
];
var chart = new dimple.chart(svg, data);
var x = chart.addMeasureAxis("x", "a");
var y = chart.addMeasureAxis("y", "b");
chart.addSeries("c", dimple.plot.bubble);
chart.draw();
// Draw a grey region using the following co-ordinates
var fromX = x._scale(210),
toX = x._scale(320),
fromY = y._scale(2200),
toY = y._scale(3100)
svg.append("rect")
.attr("x", fromX)
.attr("y", toY)
.attr("width", toX - fromX)
.attr("height", fromY - toY)
.style("fill", "grey")
.style("opacity", 0.2);
Here's the fiddle: http://jsfiddle.net/T6ZDL/7/
I have created geojson file which contains all the features of 1st floor of a shopping mall. I got that venue map projected using d3.js with different colors but only some parts not the complete map. Below is the script code and link to the geojson file. Also please note that i have not converted this geojson into topojson and used Qgis to draw the maps and c#.net to convert the geometry data to geojson objects. Can anyone please check my json and my d3.js code? Do I need to use any other projections?
https://www.dropbox.com/s/8pu2s0yamfkd89p/JSONfromDB_8Feb2014.json
$(document).ready(function () {
parseResultShopDetails();
});
function parseResultShopDetails() {
var width = 600, height = 300;
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
var projection = d3.geo.mercator()
.scale(30)
.translate([width / 2, height / 2]);
var path = d3.geo.path()
.projection(projection);
d3.json("http://localhost:1209/data/JSONfromDB_8Feb2014.json", function (error, jsonData) {
var color1 = d3.scale.category10();
svg.selectAll("path")
.data(jsonData.features)
.enter()
.append("path")
.attr("d", path)
.attr("text", function (d, i) { return "js"; })
.attr("fill", function (d, i) { return color1(i); });
});
}
It looks like the d3 mapping tools really fall apart if you try to use coordinates other than longitude and latitude.
I tried creating a "null" projection that just returns the input values, but the negative numbers and numbers greater than 360 were still getting wrapped by d3 before passing to the projection function. That avoids the trig errors from the Mercator projection, and it creates interesting art, but not the floor plan you were hoping for:
var projection = d3.geo.projection(function(λ, φ) {
return [ λ, φ ];
});
http://fiddle.jshell.net/rR2hG/1/
However, all is not lost. The second image in that example is created by just passing the array of coordinates as the points of <polygon> elements. I think that's closer to what you wanted. So you'll need to do a little more work to grab the points from the data file but you can definitely visualize them just as an array of coordinates.
svg2.selectAll("polygon")
.data(jsonData.features)
.enter()
.append("polygon")
.attr("points", function(d){ return d3.merge(d.geometry.coordinates);})
.attr("fill", function (d, i) {
return color1(i);
});
The only other suggestion is to write a script to convert your geoJSON file to geographic units. They don't have to be actual latitude and longitude of a particular place (you could still have the map centered on a reference point of your choice), but the scale has to be in degrees not feet or meters or whatever you are using.
D3's mapping projections are designed to transform 3D earth coordinates into 2D browser coordinates, so they are not that great at transforming local coordinates like the ones you've got. And as Amelia outlines your putting in coordinates that are outside of what's expected.
You'd be better off doing one of two things; creating a geometry stream based on 2 linear scales as outlined in this google groups discussion; or using d3's path generators.
To creating a 2D path generator is straightforward in d3 something like this will work:
var shops = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return xScale(d.x);
})
.y(function(d) {
return yScale(d.y);
})
The real trick here is accessing the 'right' part of your json object. If you look into the geojson structure you see that there is a geometry part as well as an properties part. You need to dig through to pull out the coordinates and then pass them to the pavement generator. In this case it would be:
d.geometry.coordinates
which would obviously need to be referenced correctly.
Note that the method outlined here isn't going to work if you have complex geometries such as multi-polygons, you'll need to do quite a bit more work. If that's what you've got you'll want to create a custom geometry stream.
Now putting all of that together here's a working example of you're json.
Hy
I'm trying to draw an svg with d3.v3.js from geojson. I fetch the geojson from openstreetmap(my test data: http://pastebin.com/4GQne42i) and try to render it to svg.
My JS code:
var path, vis, xy, jdata;
xy = d3.geo.mercator().translate([0, 0]).scale(200);
path = d3.geo.path().projection(xy);
vis = d3.select("body").append("svg").attr("width", 960).attr("height", 600);
//22.json is the name of the file which contains the geojson data
d3.json("22.json", function(error, json) {
jdata = json;
if(error!=null)
console.log(error);
return vis.append("svg:g")
.selectAll("path")
.data(json.coordinates)
.enter().append("path")
.attr("d", path);
});
And somehow my svg result is this:
<svg width="960" height="600">
<g>
<path></path>
</g>
</svg>
I know the projection is not good, but I think the svg should have nodes.
What is the problem with my code? Would you post a correct solution?
The first problem is with your data join:
vis.append("g")
.selectAll("path")
.data(json.coordinates)
.enter().append("path")
.attr("d", path);
This would mean you want one path element for each element in the json.coordinates array. Since your test data is a polygon, that would mean one path element for the exterior ring, and then perhaps multiple other path elements for any interior holes, if your data has them. I expect you just want a single path element for the entire polygon.
The second problem is that you’re not passing valid GeoJSON to the d3.geo.path instance. Because the data in your data join is json.coordinates, you’re just passing an array of coordinates directly to path, when you need to pass a GeoJSON geometry object or a feature. (See the GeoJSON specification.)
Fortunately both of these problems are easy to fix by eliminating the data join and rendering the full polygon. To add just one path element, just call selection.append:
vis.append("path")
.datum(json)
.attr("d", path);
Your projection will probably need adjusting (translate and scale), too. You might find the project to bounding box example useful here.
Do you really need to do it with D3?
I would suggest to go with more map oriented libraries like:
polymaps
Leaflet
Leaflet vector layer has support for GeoJSON and its size is quite compact.
Open Layers is also an option but it's size is quite big.
Here is an example how I have used Leaflet + GeoJSON to display suburb shape http://www.geolocation.ws/s/6798/en