Search code examples
d3.js

d3js - geojson of polygons is not drawn


I'm trying to draw a map (polygons of the districts of a city) using d3js. And all I get is a beige rectangle.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lpgz crime rates</title>
    <script src="https://d3js.org/d3.v6.min.js"></script>
    <style>
        #map {
            width: 800px;
            height: 600px;
            border: 1px solid black;
        }
        .map path {
            stroke: #999;
            stroke-width: 0.5;
        }
    </style>
</head>
<body>
    <svg id="map" viewBox="0 0 800 600"></svg>
    <script>
        const width = 800;
        const height = 600;
        const margin = 20;

        const svg = d3.select('#map')
            .attr('width', width)
            .attr('height', height);

        Promise.all([
            d3.json('data/Leipzig_ortsteile.geojson'),
            d3.json('data/crime_rates.json')
        ]).then(([ortsteile, crimeRates]) => {
            console.log("GeoJSON data:", ortsteile.features);
            console.log("Crime rates data:", crimeRates);

            const projection = d3.geoMercator()
                .fitSize([width, height], ortsteile);

            const pathGenerator = d3.geoPath().projection(projection);

            const colorScale = d3.scaleSequential(d3.interpolateReds)
                .domain([0, d3.max(Object.values(crimeRates))]);

            const paths = svg.selectAll('path')
                .data(ortsteile.features)
                .enter()
                .append('path')
                .attr('d', d => {
                    const pathData = pathGenerator(d);
                    console.log(`Path data for ${d.properties.Name}:`, pathData);
                    return pathData;
                })
                .attr('fill', d => {
                    const rate = crimeRates[d.properties.Name];
                    console.log(`Area: ${d.properties.Name}, Crime rate: ${rate}`);
                    return rate ? colorScale(rate) : '#ccc';
                })
                .attr('stroke', '#999')
                .attr('stroke-width', '0.5');

            console.log("SVG Paths:", paths);

        }).catch(err => console.error("Error loading data:", err));
    </script>
</body>
</html>

This is how the geojson looks like inside:

{
"type": "FeatureCollection",
"name": "log_lat",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "FID": 0, "OT": "00", "Name": "Zentrum" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 12.37927526130802, 51.344360566287889 ], [ 12.379117566590359, 51.344399728002436 ], [ 12.378953533569369, 51. ....

I tried multiple variations including centering at the coordinates of the city center, changing sizes... I get no errors and outputs to the console look ok (to me).


Solution

  • The issue is winding, your code is otherwise fine.

    I apologize that my comment included a reference to mapshaper - this service corrects the winding, whereas D3 requires it to be incorrect. I included it as the service is user friendly and doesn't require any code (and also is generally D3 friendly) unlike the other two verified options. But I had never used it before.

    Your file will work in almost every other mapping/plotting framework as these treat latitude longitudes as planar. A consequence of this is winding order does not matter for these frameworks: for a polygon the outside is always the portion that is unbounded and the inside is bounded.

    For D3, the latitude longitudes are on a globe, so there is no unbounded portion. The whole screen is the same color as the last feature drawn covers the entire globe except for the desired area. FitSize consequently fits the entire globe.

    One option to verify that your geojson is incorrectly wound is to manually set the projection to zoom in on your area of interest and remove any fill, you should generally see the outline of all your features (If your features are all gibberish, then you have another problem, you're projecting already projected data).

    Below I have a complete example of using Turf to rewind, realizing that my linked example isn't quite complete.

    <html>
    <head>
        <script src=" https://cdn.jsdelivr.net/npm/@turf/[email protected]/turf.min.js "></script>
        <script src="https://d3js.org/d3.v6.min.js"></script>
    </head>
    <body>
    
    <script>
    
    d3.json("target.json").then(function(data) {
        
        var fixed = data.features.map(function(feature) {
            return turf.rewind(feature,{reverse:true});
        })
            
    
        data.features = fixed;
        
        d3.select("body").append("p")
          .text(JSON.stringify(data));
        
    })
    </script>