Public
Edited
Dec 22, 2022
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
lookupMap = bartStations.features.reduce((map, feature) => {
// Make a map like {[hexagon]: [id, id, ...]}
const [lon, lat] = feature.geometry.coordinates;
const h3Index = h3.geoToH3(lat, lon, res);
if (!map[h3Index]) map[h3Index] = [];
map[h3Index].push(feature);
return map;
}, {})
Insert cell
Insert cell
function kRingResults(searchLocation) {
const lookupIndexes = kRingIndexes(searchLocation);
// Find all points of interest in the k-ring
return lookupIndexes.reduce((output, h3Index) => [...output, ...(lookupMap[h3Index] || [])], []);
}
Insert cell
function kRingIndexes(searchLocation, pad = 0) {
const searchRadiusKm = searchRadiusMiles*1.60934;
const origin = h3.geoToH3(searchLocation.lat, searchLocation.lng, res);
const searchingFor = h3.geoToH3(24.940714411344228,25.954223817201044, res)
console.log(searchingFor);

// While the h3 lib has an edge length calculation, due to distortion, it isn't right at all locations
// Further, calculating the apothem from the edge needs more-complex algebra because of the curvature of the earth
// So, we can just measure what the apothem and edge are at the origin. Each hexagon is slightly different (again, distortion),
// So we take the largest apothem for use by the inscription, and the smallest edge for the circumscription
const a = Math.max(...h3.kRingDistances(origin, 1)[1].map(
oneAwayH3 => h3.pointDist(h3.h3ToGeo(oneAwayH3), h3.h3ToGeo(origin), h3.UNITS.km) / 2))
const e = Math.min(...h3.kRingDistances(origin, 2)[2].map(
twoAwayH3 => h3.pointDist(h3.h3ToGeo(twoAwayH3), h3.h3ToGeo(origin), h3.UNITS.km) / 3))

const radiusIfEven = Math.ceil(((searchRadiusKm - e) / e) / (3.0/2.0));
const radiusIfOdd = Math.ceil(((((searchRadiusKm - e) / e) * 2.0) + 1.0)/3.0);
const inscriptionRadius = Math.ceil((searchRadiusKm - a)/(2.0*a));
const circumscriptionRadius = (radiusIfEven % 2 === 0 ? radiusIfEven : radiusIfOdd) + 1;

const kRingDistances = h3.kRingDistances(origin, circumscriptionRadius);

// We know that every h3 in the inscription are within range
const h3sFromInscription = kRingDistances.slice(0, inscriptionRadius + 1).flat();
// And the rest are somewhere in the inscription, just need to filter for the ones actually within range
const h3s = h3sFromInscription.concat(kRingDistances.slice(inscriptionRadius + 1).map(hexRing =>
hexRing.filter(newH3 => {
const h3Center = h3.h3ToGeo(newH3);
return h3.pointDist([searchLocation.lat, searchLocation.lng], h3Center, h3.UNITS.km) < (searchRadiusKm + e);
})).flat());

return h3s;
}
Insert cell
Insert cell
function haversineResults(searchLocation) {
return bartStations.features.filter(
feature => haversineDistance(
[searchLocation.lng, searchLocation.lat],
feature.geometry.coordinates,
{units: 'miles'}
) < searchRadiusMiles
);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more