简体   繁体   中英

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 US 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"]})

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM