Tengo un conjunto de coordenadas recuperadas como:
navigator.geolocation.getCurrentPosition(function(position) { let coordinates: [number, number] = [position.coords.longitude, position.coords.latitude] } // eg [-70.1234567, 40.987654]Quiero usar D3 para recuperar el condado correspondiente de un archivo TopoJSON de EE. UU. de la siguiente manera:
getGeoLocation(topology, coordinates) { topojson.feature(topology, topology["objects"]["counties"])["features"].filter(function(polygon) { polygon["geometry"]["coordinates"].forEach(coord => { console.log(d3.polygonContains(coord, coordinates)) }) }) } Sin embargo, hacer esto devuelve false para cada objeto del condado. No estoy seguro de si el mejor enfoque es usar d3.geoContains() o d3.polygonContains() , aunque en línea parece que puede haber algún problema relacionado con las características de MultiPolygon en TopoJSON (enlace aquí: https://cdn. jsdelivr.net/npm/us-atlas@3/counties-albers-10m.json ).
¿Cómo puedo resolver esto?
EDITAR
¿Quizás el problema es la falta de proyección en geoPath() que se usa en TopoJSON...? Aunque incluso si no lo proyecto y solo lo comparo con el JSON básico, el problema sigue siendo...
Algún contexto en el mapa (construido en Angular con TypeScript), donde this.topology es la importación JSON desde la URL especificada:
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 solo funciona en coordenadas esféricas (WGS84) que se especifican por longitud y latitud en grados. El TopoJSON que está utilizando proviene de esta fuente y ya se ha proyectado en un rango de píxeles mediante la proyección de Albers. Así que estos no funcionarán juntos.
La solución sería un método pathContains (hay un problema abierto al respecto). El método geoContains llama a polygonContains en una matriz de coordenadas modificada, porque los cálculos se realizan en la esfera. Solo necesitamos cálculos planos ya que las geometrías ya están proyectadas. Por supuesto, también debe proyectar las coordenadas del punto de entrada.
La implementación a continuación debería funcionar, pero ciertamente no evita todos los escollos que pueden ocurrir en 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>