Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

152
Views
Using D3 to retrieve a TopoJSON U.S. county from coordinates

I have a set of coordinates retrieved as:

navigator.geolocation.getCurrentPosition(function(position) {
  let coordinates: [number, number] = [position.coords.longitude, position.coords.latitude]
}

// e.g. [-70.1234567, 40.987654]

I want to use D3 to retrieve the corresponding county from a U.S. TopoJSON file as follows:

getGeoLocation(topology, coordinates) {
  topojson.feature(topology, topology["objects"]["counties"])["features"].filter(function(polygon) {
    polygon["geometry"]["coordinates"].forEach(coord => {
      console.log(d3.polygonContains(coord, coordinates))
    })
  })
}

Doing this returns false for every county object however. I'm not sure if the best approach is to use d3.geoContains() or d3.polygonContains(), although online it seems there may be some issue related to MultiPolygon features in the TopoJSON (link here: https://cdn.jsdelivr.net/npm/us-atlas@3/counties-albers-10m.json).

How can I resolve this?

EDIT

Perhaps the issue is lack of projection into the geoPath() being used on the TopoJSON...? Although even if I don't project through and just compare to the basic JSON, the issue remains...

Some context on the map (built in Angular with TypeScript), where this.topology is the JSON import from the specified URL:

path: any = d3.geoPath()

this.svg = d3.select("#map")
  .attr("preserveAspectRatio", "xMidYMid meet")
  .attr("viewBox", "0 0 975 610")
  .attr("height", "100%")
  .attr("width", "100%")

this.g = this.svg.append("g")
  .attr("id", "g")

this.counties = this.g.append("g")
  .selectAll("path")
  .data(topojson.feature(this.topology, this.topology["objects"]["counties"])["features"])
  .join("path")
  .attr("d", this.path)
  .attr("class", "county")
  .attr("id", function(d) {return d["id"]})
about 4 years ago · Juan Pablo Isaza
1 answers
Answer question

0

d3.geoContains works only on spherical coordinates (WGS84) that are specified by longitude and latitude in degrees. The TopoJSON you are using is from this source and already projected to a pixel range using the Albers projection. So these won't work together.

The solution would be a pathContains method (there is an open issue on this). The geoContains method calls polygonContains on a modified array of coordinates, because the calculations are done on the sphere. We only need planar calculations since the geometries are already projected. Of course, you have to project the input point coordinates, too.

The implementation below should do the trick but certainly does not circumvent all the pitfalls that may happen in general.

const projection = d3.geoAlbersUsa().scale(1300).translate([487.5, 305]);
const geo_path = d3.geoPath();

function pathContains(feature, point) {
  const coordinates = feature.geometry.coordinates;
  const n = coordinates.length;
  if (!!point) {
    for (let i = 0; i < n; i++) {
      if (d3.polygonContains(coordinates[i][0], point)) return true;
    }
  }
  return false;
}

d3.json("https://cdn.jsdelivr.net/npm/us-atlas@3/counties-albers-10m.json")
  .then(json => {
    const geo_shapes = topojson.feature(json, json.objects.states);
    function render() {
      const point = [
        d3.select("#longitude").property("value"),
        d3.select("#latitude").property("value"),
      ]
      const projected = projection(point);
      d3.select("#map")
        .selectAll("path")
        .data(geo_shapes.features, d => d.id)
        .join("path")
          .attr("d", geo_path)
          .attr("class", "map-feature")
          .attr("fill", d => pathContains(d, projected) ? "red" : "white")
          .attr("stroke", "gray");

      d3.select("circle")
        .raise()
        .attr("cx", !projected ? 0 : projected[0])
        .attr("cy", !projected ? 0 : projected[1])
        .attr("r", !projected ? 0 : 3);
    }
    d3.selectAll("input").on("input", render);
    d3.select("#map")
      .append("circle");
    render();
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.3.0/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.min.js"></script>
<input type="number" multiple="0.1" id="longitude" value="-96.6">
<input type="number" multiple="0.1" id="latitude" value="38.6">
<svg id="map" viewBox="0 0 975 610"></svg>

about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!