I have rendered the world map using world-110m topojson. I directly used the code provided by Jakob on D3 mapping tutorial - http://www.digital-geography.com/d3-js-mapping-tutorial-1-set-initial-webmap/#.V4iGN9J97IV
<!DOCTYPE html>
<meta charset="utf-8">
<!-- Set a style for our worldshape-data -->
<style>
path {
stroke: red;
stroke-width: 0.5px;
fill: grey;
}
</style>
<body>
<!-- implementation of the hosted D3- and TopoJson js-libraries -->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v0.min.js"></script>
<!-- map creation -->
<script>
// canvas resolution
var width = 1000,
height = 600;
// projection-settings for mercator
var projection = d3.geo.mercator()
// where to center the map in degrees
.center([0, 50 ])
// zoomlevel
.scale(100)
// map-rotation
.rotate([0,0]);
// defines "svg" as data type and "make canvas" command
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
// defines "path" as return of geographic features
var path = d3.geo.path()
.projection(projection);
// group the svg layers
var g = svg.append("g");
// load data and display the map on the canvas with country geometries
d3.json("world-110m.json", function(error, topology) {
g.selectAll("path")
.data(topojson.object(topology, topology.objects.countries)
.geometries)
.enter()
.append("path")
.attr("d", path)
});
// zoom and pan functionality
/*var zoom = d3.behavior.zoom()
.on("zoom",function() {
g.attr("transform","translate("+
d3.event.translate.join(",")+")scale("+d3.event.scale+")");
g.selectAll("path")
.attr("d", path.projection(projection));
});
svg.call(zoom)*/
</script>
</body>
</html>
Now i want to reuse this code to render the map of India. But when I link it to India topoJSON file, only a blank SVG is created. The js console gives an error - topojson.v0.min.js:1 Uncaught TypeError: Cannot read property 'type' of undefined
I have placed world-110m.json and india.json on dropbox -
https://www.dropbox.com/sh/wrxyngyq4jdie9c/AACG2-dTzC79rRvlxlOC5poBa?dl=0
Its just a small error.
topojson.object(topology, topology.objects.countries //wrong
topojson.object(topology, topology.objects.collection // right
Next time check the data, by printing it to console and check its contents.
Related
I am trying to draw rectangles on different map projections using d3.js and geojson. The mapped coordinates seem right, however the edges appear curved in a strange way. I understand that this may have to do with the shortest path on the real Earth, but what I would like is that the edges follow the parallels/meridians graticule of the projection. Is there a way to do that? Can anyone help?
Example: Aitoff Projection
Example: Mercator
Here is the code I am using:
<!DOCTYPE html>
<html>
<head>
<title>World Map</title>
<meta charset="utf-8">
<script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<style>
path {
fill: red;
stroke: #000;
stroke-width: .1px;
}
.graticule {
fill: none;
stroke: #000;
stroke-width: .2px;
}
.foreground {
fill: none;
stroke: #333;
stroke-width: 1.2px;
}
</style>
</head>
<body>
<svg width="960" height="600"></svg>
<script>
const svg = d3.select("svg")
const myProjection = d3.geoMercator()
const path = d3.geoPath().projection(myProjection)
const graticule = d3.geoGraticule()
const geojson = {
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "properties": {
"color": "blue"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[-80.0, 50.0], [-20.0, 50.0], [-20.0, -10.0], [-80.0, -10.0], [-80.0, 50.0]]]
} }
]
}
function drawMap(err, world) {
if (err) throw err
svg.append("g")
.selectAll("path")
.data(topojson.feature(world, world.objects.land).features)
.enter().append("path")
.attr("d", path);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
svg.append("path")
.datum(graticule.outline)
.attr("class", "foreground")
.attr("d", path);
svg.append("g")
.selectAll("path")
.data(geojson.features)
.enter().append("path")
.attr("d", path)
}
d3.json("https://unpkg.com/world-atlas#1.1.4/world/50m.json", drawMap)
</script>
</body>
</html>
Your assumption is right: d3 uses great circle distances to draw lines: this means that any path between two points using a d3 geoProjection and geoPath follows the shortest real world path between those two points. This means:
that the same path between two points aligns with other geographic points and features no matter the projection
that the anti-meridian can be accounted for
and the resulting map more accurately depicts lines.
To draw straight lines and/or lines that follow parallels (meridians are the shortest path between two points that fall on them - so paths follow this already, assuming an unrotated graticule) there are a few possibilities.
The easiest solution is to use a cylindrical projection like a Mercator to create a custom geoTransform. d3.geoTransforms do not use spherical geometry, unlike d3.geoProjections, instead they use Cartesian data. Consequently they don't sample along lines to create curved lines: this is unecessary when working with Cartesian data. This allows us to use spherical geometry for the geojson vertices within the geoTransform while still keeping straight lines on the map:
var transform = d3.geoTransform({
point: function(x, y) {
var projection = d3.geoMercator();
this.stream.point(...projection([x,y]));
}
});
As seen below:
var projection = d3.geoMercator();
var transform = d3.geoTransform({
point: function(x, y) {
var projection = d3.geoMercator();
this.stream.point(...projection([x,y]));
}
});
var color = ["steelblue","orange"]
var geojson = {type:"LineString",coordinates:[[-160,60],[30,45]]};
var geojson2 = {type:"Polygon",coordinates:[[[-160,60,],[-80,60],[-100,30],[-160,60]]]}
var svg = d3.select("body")
.append("svg")
.attr("width",960)
.attr("height",500);
svg.selectAll(null)
.data([projection,transform])
.enter()
.append("path")
.attr("d", function(d) {
return d3.geoPath().projection(d)(geojson)
})
.attr("fill","none")
.attr("stroke",function(d,i) { return color[i]; } )
.attr("stroke-width",1);
svg.selectAll(null)
.data([projection,transform])
.enter()
.append("path")
.attr("d", function(d) {
return d3.geoPath().projection(d)(geojson2)
})
.attr("fill","none")
.attr("stroke",function(d,i) { return color[i]; } )
.attr("stroke-width",2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
The orange lines use the transform, the blue lines use a plain Mercator
In some cases you could set the precision of the projection (which regulates the adaptive sampling) to some absurdly high number, this will work for some lines, but not others due to things like anti-meridian cutting:
var projection = d3.geoMercator().precision(1000000);
var transform = d3.geoTransform({
point: function(x, y) {
var projection = d3.geoMercator();
this.stream.point(...projection([x,y]));
}
});
var color = ["steelblue","orange"]
var geojson = {type:"LineString",coordinates:[[-160,60],[30,45]]};
var geojson2 = {type:"Polygon",coordinates:[[[-160,60,],[-80,60],[-100,30],[-160,60]]]}
var svg = d3.select("body")
.append("svg")
.attr("width",960)
.attr("height",500);
svg.selectAll(null)
.data([projection,transform])
.enter()
.append("path")
.attr("d", function(d) {
return d3.geoPath().projection(d)(geojson)
})
.attr("fill","none")
.attr("stroke",function(d,i) { return color[i]; } )
.attr("stroke-width",1);
svg.selectAll(null)
.data([projection,transform])
.enter()
.append("path")
.attr("d", function(d) {
return d3.geoPath().projection(d)(geojson2)
})
.attr("fill","none")
.attr("stroke",function(d,i) { return color[i]; } )
.attr("stroke-width",2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Neither approach works if you want to draw lines that are aligned with parallels on a non-cylindrical projection. For a cylindrical projection parallels are straight. The above approaches will only create straight lines. If the parallels aren't projected straight, such as the Aitoff, the lines will not align with the graticule.
To have a line follow a parallel you will need to sample points along your paths because the projected parallels will not all be straight and parallels don't follow great circle distance. Therefore neither the default projection nor the method above will work in these instances.
When sampling you will need to treat the data as Cartesian - essentially using a cylindrical projection (Plate Carree) to have lines follow parallels.
I would like to minimize greenland and antartica on my D3 geojson map. How do I do this. I've tried scale and translate methods but they simply move the map around on the page not providing the minimized y coordinates.
image of map
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="d3.v3.js"></script>
<script src="topojson.min.js"></script>
<style>
</style>
<script type="text/javascript">
function draw(geo_data) {
"use strict";
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin)
.attr("height", height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geo.mercator();
var path = d3.geo.path().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgb(9, 157, 217)')
.style('stroke', 'black')
.style('stroke-width', 0.5);
};
</script>
</head>
<body>
<script type="text/javascript">
/*
Use D3 to load the GeoJSON file
*/
//d3.json("world-topo-min.json", draw);
d3.json("world_countries.json", draw);
</script>
</body>
</html>
If you want to remove the areas for Greenland and Antarctica on the fly, you can simply filter your GeoJSON FeatureCollection, i.e. geo_data.features. In this array you will find features for both Greenland ("id": "GRL") as well as Antarctica ("id": "ATA"). Hence, you can make use of the array's .filter() method to get rid of these two features leaving the rest untouched:
var map = svg.selectAll('path')
.data(geo_data.features.filter(d => d.id !== "GRL" && d.id !== "ATA))
I have used the code from github (https://github.com/alignedleft/d3-book/blob/master/chapter_12/04_fill.html).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3: Setting path fills</title>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<style type="text/css">
/* No style rules here yet */
</style>
</head>
<body>
<script type="text/javascript">
//Width and height
var w = 500;
var h = 300;
//Define map projection
var projection = d3.geo.albersUsa()
.translate([w/2, h/2])
.scale([500]);
//Define path generator
var path = d3.geo.path()
.projection(projection);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
//Load in GeoJSON data
d3.json("us-states.json", function(json) {
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", "steelblue");
});
</script>
</body>
</html>
JSON file is from ( https://github.com/alignedleft/d3-book/edit/master/chapter_12/us-states.json).
Sample:
{"type":"FeatureCollection","features":[
{"type":"Feature","id":"01","properties":{"name":"Alabama"},"geometry":{"type":"Polygon","coordinates":[[[-87.359296,35.00118],[-85.606675,34.984749],[-85.431413,34.124869],[-85.184951,32.859696],[-85.069935,32.580372],[-84.960397,32.421541],[-85.004212,32.322956],[-84.889196,32.262709],[-85.058981,32.13674],[-85.053504,32.01077],[-85.141136,31.840985],[-85.042551,31.539753],[-85.113751,31.27686],[-85.004212,31.003013],[-85.497137,30.997536],[-87.600282,30.997536],[-87.633143,30.86609],[-87.408589,30.674397],[-87.446927,30.510088],[-87.37025,30.427934],[-87.518128,30.280057],[-87.655051,30.247195],[-87.90699,30.411504],[-87.934375,30.657966],[-88.011052,30.685351],[-88.10416,30.499135],[-88.137022,30.318396],[-88.394438,30.367688],[-88.471115,31.895754],[-88.241084,33.796253],[-88.098683,34.891641],[-88.202745,34.995703],[-87.359296,35.00118]]]}},...
But i'm getting an empty HTML page as output. What am i missing?
No i don't see any issues with the code except may be us-states.json be not available in the root folder, where your code is looking for.
You can also access the us-states.json directly from github like this:
d3.json("https://raw.githubusercontent.com/alignedleft/d3-book/master/chapter_12/us-states.json", function(json) {
//Bind data and create one path per GeoJSON feature
svg.selectAll("path")
.data(json.features)
.enter()
.append("path")
.attr("d", path)
.style("fill", "steelblue");
});
Working code here
I want to display a map with d3 but the path is not drawn in the browser although in the developer tools I see that the topojson file is loaded so there is data for the path. I just get a blank page. What could be the problem?
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
fill: none;
stroke: #000;
stroke-linejoin: round;
stroke-linecap: round;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 600;
var path = d3.geo.path()
.projection(null);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("build/immoscout.topojson", function(error, us) {
if (error) return console.error(error);
svg.append("path")
.datum(topojson.mesh(us))
.attr("d", path);
});
Is your question answered based on Lars' comment, "You are calling .projection(null). You need to set one of D3's projections there"? Few projection options listed below. You may also want to check and make sure that your server can run .topojson files. See How to allow download of .json file with ASP.NET
Extension: .json
MIME type: application/json
Extension: .geojson
MIME type: application/json
Extension: .topojson
MIME type: application/json
1) Mollweide projection shows the entire world
var width = 500;
var height = 500;
var projection = d3.geo.mollweide()
.scale(120)
.translate([width / 2, height / 2]);
var geoPath = d3.geo.path().projection(projection);
2) Mercator projection, which has became standard used by Google Maps
var width = 500;
var height = 500;
var aProjection = d3.geo.mercator()
.scale(80)//80 works well in this case
.translate([width / 2, height / 2]);
var geoPath = d3.geo.path().projection(aProjection);//d3.geo.path() defaults to albersUSA, which is a projection suitable only for maps of the United States
`
I'm having an issue with a d3 zoomable map.
I'm loading the map from a previously built topojson file with a departmentsobject (the areas in the map) and a maternidadesobject (a few points in the map, initially rendered with crosses).
I'm using d3.behavior.zoom to implement the zoom behaviour, and I want it to be able to zoom using the mouse wheel and pan with drag. It works just fine with the map itself (the areas). However, the points in the map get shifted instantly to a wrong location at any zoom event. Also, the points' path is changed from crosses to circles somehow!
You can reproduce the issue and view the code here: http://bl.ocks.org/monsieurBelbo/5033491
Here is code:
<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.js"></script>
<script src="topojson.v0.min.js"></script>
<html>
<style>
.background {
fill: none;
pointer-events: all;
}
.department {
fill: #aaa;
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<body>
<script>
d3.json("santafe.json", function(error, theProvince) {
var width= 960, height= 500;
var svg = d3.select("body").append("svg");
var departments = topojson.object(theProvince, theProvince.objects.departments);
// The projection
var projection = d3.geo.mercator()
.scale(14000)
.center([-60.951,-31.2])
.translate([width / 2, height / 2]);
// The path
var path = d3.geo.path()
.projection(projection);
// Zoom behavior
var zoom = d3.behavior.zoom()
.translate(projection.translate())
.scaleExtent([height, Infinity])
.scale(projection.scale())
.on("zoom", function() {
projection.translate(d3.event.translate).scale(d3.event.scale)
map.selectAll("path.zoomable").attr("d", path);
});
// The map
var map = svg.append("g")
.classed("provinceMap", true)
.call(zoom);
map.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
// Departments
map.selectAll(".department")
.data(departments.geometries)
.enter().append("path")
.classed("department", true)
.classed("zoomable", true)
.attr("d", path);
// Places
map.selectAll(".place-label")
.data(topojson.object(theProvince, theProvince.objects.maternidades).geometries)
.enter().append("path")
.classed("place", true)
.classed("zoomable", true)
.attr("d", d3.svg.symbol().type("cross"))
.attr("transform", function(d) { return "translate(" + projection(d.coordinates.reverse()) + ")"; });
});
</script>
</body>
</html>
Any ideas?
Thanks!
UPDATE
Thanks to #enjalot 's suggestion, the issue was solved by re-translating the places on the zoom behaviour. Just add:
map.selectAll(".place").attr("transform", function(d) { return "translate(" + projection(d.coordinates) + ")"; });
to the zoom behavior. Check out a working version here: http://tributary.io/inlet/5095947