Reputation: 667
I'm new to D3js and GeoJSON and would like to know how can I can render my polygons from GeoJSON file.
My GeoJSON file comes from ESRI Shapefile without prj File, so I have no Information about the projection but its definitely not WGS 84.
With an third party tool I can convert my GeoJSON to SVG and the format looks like this: (I have more then 65000 paths.)
<path style="fill=blue;stroke-width:0.035;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:10;" d="M 297.378906 316.671875 L 298.539063 316.628906 " transform="matrix(1,0,0,-1,0,842)"/>
When I open this svg file it looks like this, which is correct:
So now I would like to generate these paths with d3js from GeoJSON file directly but it's somehow messed up:
This is my Code so far:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 960,
height = 1160;
var svgContainer = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "2px solid steelblue");
const xhr = new XMLHttpRequest();
xhr.open('GET', 'poly.json');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'json';
xhr.onload = function () {
if (xhr.status !== 200) return
var projection = d3.geoAlbers()
.scale(1)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var b = path.bounds(xhr.response),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
svgContainer.append("rect")
.attr('width', width)
.attr('height', height)
.style('stroke', 'black')
.style('fill', 'orange');
svgContainer.selectAll("path").data(xhr.response.features).enter().append("path")
.attr("d", path)
.style("fill", "red")
.style("stroke-width", "1")
.style("stroke", "blue")
};
xhr.send();
</script>
Can anyone tell me how I can generate my paths like the third party tool with d3js?
EDIT: Here is my GeoJSON data: https://jsonblob.com/b6664214-4b12-11e9-bbf0-3d8b9aa005c2
Upvotes: 2
Views: 1041
Reputation: 38151
You are using a projection: d3.geoAlbers()
that takes latitude/longitude pairs and projects them to a Cartesian grid. But, you also note that your data does not use latitude/longitude pairs (omitting details, therefore it doesn't use WGS84). All d3.geoProjections take spherical coordinates and project them to two dimensional grids, but your data is Cartesian already, so we need a different approach.
Instead, use a d3.geoIdentity()
, which in its default form is the same as a null projection: values are treated as pixel values and are mapped as such. But, it also allows us to tap some standard d3.geoProjection methods, such as scale and translate.
More importantly it lets us access the method .fitSize (or fitExtent), which lets us translate and scale the identity transform such that our feature is centered and scaled to our map size. The fitSize method takes a width and height in pixels, the area in which to display the features, and a geojson object (not an array of features).
var projection = d3.geoIdentity()
.fitSize([width,height],geojsonObject)
Now as many projected coordinate systems have [0,0] in the bottom left, but SVG has [0,0] in the top left, we can also apply a flip on the y axis if necessary:
var projection = d3.geoIdentity()
.reflectY(true)
.fitSize([width,height],geojsonObject)
Here's a demo without y reflection (you'll need it most likely)
This method ships with d3v4+, and lets us avoid the use of the old v3 manual way of centering and scaling objects:
var projection = d3.geoAlbers()
.scale(1)
.translate([0, 0]);
var path = d3.geoPath()
.projection(projection);
var b = path.bounds(xhr.response),
s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];
projection
.scale(s)
.translate(t);
But, if you wish to overlay other data with your features, you'll need to either have data that has the same projection, or you need to know what the projection of your data is so that you can reproject some or all of the datasets to a common format. If I had to guess, I'd say that your projection is UTM zone 32N (based on field names). Generally if you have a north/south value in the millions, you are dealing with UTM as this represents meters from the equator. If placed with UTM zone 32N, you might be looking at:
I am intrigued now, assuming I got the projection, I'm guessing cemetery plots as the plots are small, 1.3m x 3m, and their alignment suggests not buildings, and it has placement in what looks like dedicated green space, and other uses would be too dense when compared with the surrounding area
If this looks correct, then you could use this as a prj file (the datum may be incorrect, which may induce some shifting:
PROJCS["WGS_1984_UTM_Zone_32N",GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",9.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]]
Upvotes: 2