Public
Edited
Nov 7, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
testHull = ({
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: [[
[83.236170244, -11.878236624],[83.789142665, -37.336889849],[78.337085641, -70.178726021],[74.973370110, -68.835747847],[60.791849077, -60.103975791],[58.783716940, -53.984522566],[55.043628385, -38.479355392],[55.515684515, -27.149106353],[58.261350008, -11.600942879],[60.492378337, -7.990497334],[73.020683297, 2.438747422],[76.040254248, 2.393050047],[83.236170244, -11.878236624]
].map(c => c.reverse())]
}
})
Insert cell
function getEdgeChildren(parent, res) {
const parentRes = h3.getResolution(parent);
const resDiff = res - parentRes;
if (!resDiff) return [parent];

const leftNeighborHex = [0, 5, 3, 1, 6, 4, 2];
const rightNeighborHex = [0, 3, 6, 2, 5, 1, 4];

const out = [];
function getEdges(parent, direction, skipNeighbor) {
const parentRes = h3.getResolution(parent);
const childRes = parentRes + 1
// More expensive replacement for childPosToCell
const children = h3.cellToChildren(parent, childRes);
const cell = children[direction];
if (childRes === res) {
out.push(cell);
} else {
getEdges(cell, direction);
}
if (!skipNeighbor) {
const neighbors = childRes % 2 ? rightNeighborHex : leftNeighborHex;
const neighborDir = neighbors[direction];
const neighbor = children[neighborDir];
if (childRes === res) {
out.push(neighbor);
} else {
getEdges(neighbor, neighborDir);
}
}
}

function getPentagonEdges(parent, direction) {
const parentRes = h3.getResolution(parent);
const childRes = parentRes + 1;
// More expensive replacement for childPosToCell
const children = h3.cellToChildren(parent, childRes);
const cell = children[direction];
if (childRes === res) {
out.push(cell);
} else {
getEdges(cell, direction + 1);
}
}

if (h3.isPentagon(parent)) {
for (let i = 1; i < 6; i++) {
getPentagonEdges(parent, i);
}
} else {
for (let i = 1; i < 7; i++) {
getEdges(parent, i, true);
}
}
return out;
}
Insert cell
function getEdgeChildPos(parent, res) {
const parentRes = h3.getResolution(parent);
const resDiff = res - parentRes;
if (!resDiff) return [];
const out = [];

if (h3.isPentagon(parent)) {
for (let i = 0; i < 5; i++) {
let offset = 1 + 5 * (7 ** (resDiff - 1) - 1) / 6;
for (let d = resDiff - 1; d >= 0; d--) {
offset += (d < resDiff - 1 ? i + 2 : i) * 7 ** d;
}
out.push(offset);
}
} else {
// hexagons - simple powers of 7
for (let i = 1; i < 7; i++) {
let offset = 0;
for (let d = resDiff - 1; d >= 0; d--) {
offset += i * 7 ** d;
}
out.push(offset);
}
}
return out;
}
Insert cell
function getEdgeChildrenSimple(parent, res) {
const posIndexes = getEdgeChildPos(parent, res);
const children = h3.cellToChildren(parent, res);
return posIndexes.map(offset => {
const cell = children[offset];
if (!cell) throw new Error(`No cell for index ${offset}`);
return cell;
})
}
Insert cell
Insert cell
function getTestPolygon(parent, res) {
const boundary = h3.cellToBoundary(parent, true);
const [centerLat, centerLng] = h3.cellToLatLng(parent);
const earthRadiusKm = 6371.007180918475;
const resDiff = res - h3.getResolution(parent);
const offset = h3.radsToDegs(h3.getHexagonEdgeLengthAvg(res, h3.UNITS.km) / earthRadiusKm) * resDiff;
const coordinates = [];

function applyOffset([lng, lat]) {
lat += offset * (lat < centerLat ? -1 : 1);
lng += offset * (lng < centerLng ? -1 : 1) * 2;
return [lng, lat];
}

if (resDiff > 2) {
const candidates = getEdgeChildrenSimple(parent, res);
const childOrderCCWHex = [5, 1, 2, 0, 4, 3];
const childOrderCCWPent = [4, 0, 1, 3, 2];
const order = h3.isPentagon(parent) ? childOrderCCWPent : childOrderCCWHex;
for (let i = 0; i < candidates.length; i++) {
const [lat, lng] = h3.cellToLatLng(candidates[order[i]]);
coordinates.push(applyOffset(boundary[i]), applyOffset([lng, lat]));
}
coordinates.push(coordinates[0]);
} else if (resDiff > 0) {
for (let i = 0; i < boundary.length; i++) {
coordinates.push(applyOffset(boundary[i]));
}
}

return {
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: [coordinates]
}
};
}
Insert cell
Insert cell
function getTestPolygon2(parent) {
const positions = [
// Hex
[
// Res offset 2
[
// Class II
[48, 44, 16, 17, 24, 22, 8, 12, 40, 39, 32, 34],
// Class III
[46, 48, 20, 16, 23, 24, 10, 8, 36, 40, 33, 32]
],
// Res offset 4
[
// Class II
[2400, 2200, 800, 850, 1200, 1100, 400, 600, 2000, 1950, 1600, 1700],
// Class III
[2300, 2400, 1000, 800, 1150, 1200, 500, 400, 1800, 2000, 1650, 1600]
]
],
// Pentagon
[
// Res offset 2
[
// Class II
[40, 36, 8, 9, 16, 14, 32, 31, 24, 26],
// Class III
[38, 40, 12, 8, 15, 16, 28, 32, 25, 24]
],
// Res offset 4
[
// Class II
[2000, 1800, 400, 450, 800, 700, 1600, 1550, 1200, 1300],
// Class III
[1900, 2000, 600, 400, 750, 800, 1400, 1600, 1250, 1200]
]
]
];
const earthRadiusKm = 6371.007180918475;

const parentRes = h3.getResolution(parent);
const edgeRes = Math.min(parentRes + 4, 15);
let resDiff = edgeRes - parentRes;
resDiff = resDiff < 2 ? resDiff : resDiff < 4 ? 2 : 4;

const [centerLat, centerLng] = h3.cellToLatLng(parent);
const offset = h3.radsToDegs(h3.getHexagonEdgeLengthAvg(parentRes + resDiff, h3.UNITS.km) / earthRadiusKm) * resDiff;

function applyOffset([lng, lat]) {
lat += offset * (lat < centerLat ? -1 : 1) * 1.5;
lng += offset * (lng < centerLng ? -1 : 1) * 1.5;
return [lng, lat];
}

function applyOffset2([lng, lat]) {
const dist = Math.sqrt(Math.pow(centerLat - lat, 2) + Math.pow(centerLng - lng, 2));
lat = lat + (lat - centerLat) / dist * offset * 3;
lng = lng + (lng - centerLng) / dist * offset * 3;
return [lng, lat];
}

const coordinates = [];

if (resDiff < 2) {
const boundary = h3.cellToBoundary(parent, true);
for (let i = 0; i < boundary.length; i++) {
coordinates.push(applyOffset(boundary[i]));
}
} else {
const pos = positions[h3.isPentagon(parent) ? 1 : 0][resDiff < 4 ? 0 : 1][parentRes % 2];
const children = h3.cellToChildren(parent, parentRes + resDiff);
const candidates = pos.map(p => children[p]);
for (let i = 0; i < candidates.length; i++) {
const [lat, lng] = h3.cellToLatLng(candidates[i]);
coordinates.push(applyOffset([lng, lat]));
}
coordinates.push(coordinates[0]);
}

return {
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: [coordinates]
}
};
}
Insert cell
edgeChildren2 = {
return edgeChildren.filter((_, i) => !(i % 8) || !((i + 1) % 8)) //.map(cell => hexagons.indexOf(cell));
}
Insert cell
Insert cell
{
const childOrderCCWHex = [10,11, 2, 3, 4,5, 0, 1, 8, 9, 6, 7];
const childOrderCCWPent = [8, 9, 0, 1, 2, 3, 6,7, 4, 5];
function resort(pos, order) {
const out = [];
for (let i = 0; i < pos.length; i++) {
out.push(pos[order[i]]);
}
return out
}
return {hex:
[
// class II
[8, 10, 16, 20, 24, 23, 32, 33, 40, 36, 48, 46],
// class III
[8, 12, 16, 17, 24, 22, 32, 34, 40, 39, 48, 44],
// odd res
[400, 500, 800, 1000, 1200, 1150, 1600, 1650, 2000, 1800, 2400, 2300],
// even res
[400, 600, 800, 850, 1200, 1100, 1600, 1700, 2000, 1950, 2400, 2200]
].map(pos => resort(pos, childOrderCCWHex)),
pent:
[
// class III
[8, 12, 16, 15, 24, 25, 32, 28, 40, 38],
// class II
[8, 9, 16, 14, 24, 26, 32, 31, 40, 36],
// odd res
[400, 600, 800, 750, 1200, 1250, 1600, 1400, 2000, 1900],
// even res
[400, 450, 800, 700, 1200, 1300, 1600, 1550, 2000, 1800],
].map(pos => resort(pos, childOrderCCWPent))
}
}
Insert cell
hull = {
return getTestPolygon2(parent, h3Resolution);
const children = geojson2h3.h3SetToMultiPolygonFeature(edgeChildren);
return fixTransmeridian(convexHull(children));
}
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