JavaScript and D3¶
OpenSoil's CORS policy supports public, credential-free browser reads. A location response is JSON rather than GeoJSON because it can contain many horizon/property observations at one coordinate. Build a GeoJSON feature only after choosing the property, depth, component, and aggregation appropriate to your visualization.
Plot a queried point¶
<svg id="soil-map" width="720" height="420" aria-label="Queried soil location"></svg>
<script type="module">
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const location = { latitude: 39.7102, longitude: -111.8363 };
const params = new URLSearchParams({
...location,
properties: "ph",
depth_top_cm: "0",
depth_bottom_cm: "30",
});
const response = await fetch(
`https://api.opensoil.net/v1/soil/location?${params}`,
);
if (!response.ok) throw new Error(`OpenSoil returned ${response.status}`);
const result = await response.json();
const observation = result.observations.find(
(item) => item.property.id === "ph",
);
if (!observation) throw new Error("No pH value was reported for this interval");
const feature = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [location.longitude, location.latitude],
},
properties: {
value: observation.value,
unit: observation.unit,
dataKind: observation.data_kind,
provider: observation.source.provider,
warning: observation.warnings[0],
},
};
const svg = d3.select("#soil-map");
const projection = d3.geoAlbersUsa().fitExtent(
[[30, 30], [690, 390]],
{ type: "FeatureCollection", features: [feature] },
);
const [x, y] = projection(feature.geometry.coordinates);
svg.append("circle")
.attr("cx", x)
.attr("cy", y)
.attr("r", 9)
.attr("fill", d3.scaleSequential([4, 9], d3.interpolateRdYlBu)(feature.properties.value));
svg.append("text")
.attr("x", x + 14)
.attr("y", y + 5)
.text(`pH ${feature.properties.value} · ${feature.properties.dataKind}`);
</script>
For a real national map, supply your own basemap geometry and projection bounds. A single point cannot meaningfully fit a national projection on its own.
Visualization rules¶
- Never plot all horizon rows as if they were independent locations.
- Choose and label a depth interval.
- Decide how multiple mapped components are represented; do not treat component percent as point probability.
- Keep
data_kindvisible in legends or tooltips. - Display provider and retrieval time.
- Do not suppress warnings because they are inconvenient for the chart.
- Avoid cross-provider color scales until units, methods, depths, and support are compatible.
Area geometries and bulk map layers are roadmap work. The current endpoint is suitable for point inspection and small teaching prototypes, not high-volume map tiling.