0

I have a USA country with states map drawn using d3.js. I want to show few locations on this map (as dots or small circles). The following code shows the map with states. I have the longitude and latitude values for a few locations. How do I draw these locations on this map?

   (async function getMapData() {
        const DATA_URL = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-albers-10m.json";
        let resp = await fetch(DATA_URL);
        const topoJson = await resp.json();
        const geoJson = await topojson.feature(topoJson, topoJson.objects.states).features
        await drawMap(geoJson);
        console.log("Done");
    })();

   async function drawMap(data) {

      const w = 1000;
      const h = 800;

      const svg = d3.select("body")
                    .append("svg")
                    .attr("width", w)
                    .attr("height", h);

      svg.append("g")
                .selectAll("path")
                .data(data)
                .join("path")
                .attr("d", d3.geoPath())
                .style("fill", "none")
                .style("stroke", "black")
                .style("stroke-width", "1px");


      const locations = [
        { long: -73.9249, lat: 40.6943 },
        { long: -118.4068, lat: 34.1141 },
        { long: -96.7667, lat: 32.7935 }
      ];

      // Show the locations on the map

   }
    .locations {
      fill: blue;
    }
  <script src='https://d3js.org/d3.v6.min.js'></script>
  <script src="https://unpkg.com/topojson@3"></script>

I had tried using the following as an example with my data and the newer versions of d3js - no success: https://plnkr.co/edit/hCYQKhbJKvbefIa0uncC?preview

prasad_
  • 12,755
  • 2
  • 24
  • 36

1 Answers1

1

I think this is a bit tricky since you're mixing a pre-projected TopoJSON with lat/lon data. It would be a bit easier if you used non-projected map data. You could then define whatever projection you want and apply it to both the map and point data. Of course, there's an efficiency trade off, since the browser has to project all the points, but I find that's barely noticeable for data of this size.

Having said all that, according to Mike Bostock, the Albers projected TopoJSON that you're using was created with the following projection:

d3.geoAlbersUsa()
  .scale(1300)
  .translate([487.5, 305])

Thus, we should be able to apply that projection to your points. Putting it all together looks like so:

   (async function getMapData() {
        const DATA_URL = "https://cdn.jsdelivr.net/npm/us-atlas@3/states-albers-10m.json";
        let resp = await fetch(DATA_URL);
        const topoJson = await resp.json();
        const geoJson = await topojson.feature(topoJson, topoJson.objects.states);
        drawMap(geoJson, topoJson.bbox);
    })();

   function drawMap(data, bbox) {

      const w = 975;
      const h = 610;


      const svg = d3.select("body")
        .append("svg")
        .attr('viewBox', [0,0,w,h])
        .style('border', 'solid 1px black')
       // .attr("width", w)
       // .attr("height", h);

      svg.append("g")
        .selectAll("path")
        .data(data.features)
        .join("path")
        .attr("d", d3.geoPath())
        .style("fill", "none")
        .style("stroke", "black")
        .style("stroke-width", "1px");

      const locations = [
        { long: -73.9249, lat: 40.6943 },
        { long: -118.4068, lat: 34.1141 },
        { long: -96.7667, lat: 32.7935 }
      ];

      // Show the locations on the map
      
      let p = d3.geoAlbersUsa()
        .scale(1300).translate([487.5, 305])


      let plocs=locations.map(pt => p([pt.long,pt.lat]));
      svg.selectAll("circle")
        .data(plocs)
        .join('circle')
        .attr('cx', d => d[0])
        .attr('cy', d => d[1])
        .attr('r', 6)
        .attr('fill', 'red')
        .attr('stroke', 'black')

   }
    .locations {
      fill: blue;
    }
  <script src='https://d3js.org/d3.v6.min.js'></script>
  <script src="https://unpkg.com/topojson@3"></script>
Mark McClure
  • 4,862
  • 21
  • 34
  • I noted that one of the long/lat `{ long: -73.9249, lat: 40.6943 }` points to Brooklyn, New York - as per this https://www.latlong.net/Show-Latitude-Longitude.html . The above code shows somewhere in Pennsylvania. – prasad_ Jul 26 '23 at 05:05
  • Yeah, I wondered about that since I wasn't sure where the points were really supposed to go. I researched the TopoJSON file that you're using and edited the answer. More generally, though, I really think it's much easier to work with [lat/lng data](https://github.com/topojson/us-atlas#states-10m.json); that way, you've got total control over whatever projection you want to use.. – Mark McClure Jul 26 '23 at 11:58
  • I see that working with data with projection and non-projected data together can be challenging. I was also referring this answer with related discussion: https://stackoverflow.com/questions/58649544/d3-js-v5-unable-to-get-data-points-to-show-on-map-of-us – prasad_ Jul 26 '23 at 12:37