Public
Edited
Jan 30
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
sampleImage.naturalHeight
Insert cell
Insert cell
Insert cell
worldMapInfo
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
[...sampleTraceResult.polygonRings.values()].flat(2).map(c => sampleTraceResult.coordTypes.get(c)).filter(v => v)
Insert cell
Insert cell
proj = getMapGeoProjection(d3.geoMiller, [sampleImage.naturalWidth, sampleImage.naturalHeight], mapGeoExtent)
Insert cell
Insert cell
sampleTopology.properties.junctions.filter(c => c[0] == 2048)
Insert cell
sampleTopology = buildTopology(sampleTraceResult, { allowExpansion: false, wrapX: sampleOptions.wrapX /*, projection: proj */});
Insert cell
_sampleTopology = sampleTopology
Insert cell
topojson.merge(sampleTopology, sampleTopology.objects.traced.geometries)
Insert cell
sampleTopology.arcs.flat().length
// 97357
Insert cell
Insert cell
sampleTopology.objects.traced.geometries.filter(g => g.type == "Polygon").map(p => p.arcs).map(a => a.join(","))
Insert cell
_.uniq(sampleTopology.objects.traced.geometries.map(g => g.type))
Insert cell
sampleTopologyPolygons = {
const sampleTopology = _.cloneDeep(_sampleTopology);
const shiftedArcIds = { left: new Map(), right: new Map() };
function getShiftedArcId(arcId, edge) {
if (arcId < 0) return ~getShiftedArcId(~arcId, edge);
if (shiftedArcIds[edge].has(arcId)) return shiftedArcIds[edge].get(arcId);

let shift = ((edge == "left") ? 1 : -1) * sampleTopology.bbox[2];
if (!sampleTopology.arcs[arcId]) debugger;
let newArcId = sampleTopology.arcs.length;
let newArc = sampleTopology.arcs[arcId].map(c => [c[0] + shift, c[1]]);
sampleTopology.arcs.push(newArc);
shiftedArcIds[edge].set(arcId, newArcId);
return newArcId;
}

function clonePolygon(sourceGeometry, polygonArcs) {
return {
type: "Polygon",
arcs: polygonArcs.map(a => [...a]),
properties: {...sourceGeometry.properties}
};
}
function extractPolygons(geometry) {
if (geometry.type == "Polygon") {
return [clonePolygon(geometry, geometry.arcs)];
}
if (geometry.type == "MultiPolygon") {
return geometry.arcs.map(polygonArcs => clonePolygon(geometry, polygonArcs));
}
return [];
}

const mapEdgeArcIds = { left: new Set(), right: new Set() };
const leftEdge = 1;
const rightEdge = sampleTopology.bbox[2] - 1;
sampleTopology.arcs.forEach((arc, i) => {
let isLeft = false, isRight = false;
for (let coord of arc) {
isLeft ||= coord[0] <= leftEdge;
isRight ||= coord[0] >= rightEdge;
}
if (isLeft) {
mapEdgeArcIds.left.add(i);
mapEdgeArcIds.left.add(~i);
}
if (isRight) {
mapEdgeArcIds.right.add(i);
mapEdgeArcIds.right.add(~i);
}
});

const polygonIdsToMerge = new Set();
const currentPolygons = sampleTopology.objects.traced.geometries.map(g => extractPolygons(g)).flat();
const newPolygons = [];
for (let polygon of currentPolygons) {
const arcIds = polygon.arcs.flat();
for (let edge of ["left", "right"]) {
if (arcIds.some(id => mapEdgeArcIds[edge].has(id))) {
const newArcs = arcIds.map(id => getShiftedArcId(id, edge));
newPolygons.push({
type: "Polygon",
arcs: newArcs,
properties: {...polygon.properties, isGenerated: true, sourceEdge: edge, _arcs: newArcs.map(id => sampleTopology.arcs[id]) }
});
polygonIdsToMerge.add(polygon.properties.id);
}
}
}

const polyGroups = _.groupBy(currentPolygons.concat(newPolygons), p => polygonIdsToMerge.has(p.properties.id));
const polygonsToKeep = polyGroups["false"];
const polygonsToMerge = _.groupBy(polyGroups["true"], p => p.properties.id);
//return Object.values(polygonsToMerge)[0].filter(p => p.properties.isGenerated);
//return topojson.mergeArcs(sampleTopology, Object.values(polygonsToMerge)[0]);
//return { sampleTopology, polyGroups, polygonsToKeep, polygonsToMerge };
/*
for (let polygon of currentPolygons) {
if (!polygonIdsToMerge.has(polygon.properties.id)) {
finalPolygons.push(polygon);
continue;
}
else {
}
}
*/
return { currentPolygons, newPolygons, mapEdgeArcIds, polygonIdsToMerge };
}
Insert cell
mapEdgeArcIds = {
const mapEdgeArcIds = { left: [], right: [] };
const rightEdge = sampleTopology.bbox[2];
sampleTopology.arcs.forEach((arc, i) => {
let isLeft = false, isRight = false;
for (let coord of arc) {
isLeft ||= coord[0] == 0;
isRight ||= coord[0] == rightEdge;
}
if (isLeft) mapEdgeArcIds.left.push(i);
if (isRight) mapEdgeArcIds.right.push(i);
});
return mapEdgeArcIds;
}
Insert cell
Insert cell
Insert cell
async function gpuProcessTile(bitmap, tileRect, mapRect, reglProgram) {
const { regl, commands, buffers } = reglProgram;
const tileTexture = regl.texture({ data: bitmap });
const tileDimensions = [ bitmap.width, bitmap.height ];

const mapEdgesAbs = {
x0: mapRect.x,
y0: mapRect.y,
x1: mapRect.x + mapRect.width,
y1: mapRect.y + mapRect.height
};
const mapBboxVec = [
mapEdgesAbs.x0 - tileRect.x,
mapEdgesAbs.y0 - tileRect.y,
mapEdgesAbs.x1 - tileRect.x - 1,
mapEdgesAbs.y1 - tileRect.y - 1
]
const tileBboxVec = [
TILE_PADDING,
TILE_PADDING,
TILE_PADDING + tileRect.width - 1,
TILE_PADDING + tileRect.height - 1
]
Object.values(buffers).forEach(b => b.resize(...tileDimensions));

commands.replaceColors({ uSampler: tileTexture, framebuffer: buffers.color });
await glClientWaitAsync(regl);
tileTexture.destroy();

commands.traceImage({ uSampler: buffers.color, mapBboxVec, tileBboxVec, framebuffer: buffers.traced });
await glClientWaitAsync(regl);
const results = [];
const rect = Bbox.dims(tileBboxVec);
if (mapRect.width == tileRect.x + tileRect.width) rect.width++;
if (mapRect.height == tileRect.y + tileRect.height) rect.height++;
const data = await readFramebufferAsync(regl, buffers.traced, rect);
return new PixelIO(rect.width, rect.height, data);
}
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
Insert cell
Insert cell
parseGpuTileResult(sampleTileGpuResult)
Insert cell
Insert cell
getMapEdgeCoords({n: true, e: true, s: true, w: true}, { x: 5, y: 5, width: 10, height: 10 })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
_.max(sampleTopology.properties.junctions.map(j => j[0]))
Insert cell
sampleTopology.arcs.flat().length
// 97012
Insert cell
CoordMask.fromArray([0.5, 0.5])
Insert cell
function presimplifyProtectJunctions(topology) {
const coordTypes = topology.properties.coordTypes;
const junctionCoordNums = [...coordTypes.numericMap.entries()].filter(e => e[1] == "junction").map(e => e[0]);
const junctions = MaskSet.around(new Set(junctionCoordNums), CoordMask);

function weight(triangle) {
if (junctions.has(triangle[1])) return Infinity;
return topojson.planarTriangleArea(triangle);
}

const result = topojson.presimplify(topology, weight);
result.properties = topology.properties;
return result;
}
Insert cell
{
let a = [1,2,3,4,5,1];
rotateRing(a, -2)
return a;
}
Insert cell
function coordsEqual(a,b) {
if (a === b) return true;
if (a === undefined || b === undefined) return false;
return a[0] == b[0] && a[1] == b[1];
}
Insert cell
function rotateRing(ring, newStartIndex) {
if (newStartIndex <= 0) return;
const newRing = [
...ring.slice(newStartIndex, -1),
...ring.slice(0, newStartIndex),
ring[newStartIndex]
];
ring.splice(0, ring.length, ...newRing);
}
Insert cell
_.min([1,2,3,undefined])
Insert cell
sampleTopology
Insert cell
sampleGeoJson
Insert cell
weightCounts = {
const topology = sampleTopology;
const usedArcStats = [];

for (let geometry of sampleTopology.objects.traced.geometries) {
const arcIds = geometry.arcs.flat().map(a => (a < 0) ? ~a : a).flat();
arcIds.forEach(i => usedArcStats.push(arcStats[i]));
}
let weightCounts = _.groupBy(usedArcStats.flat(), "value");
weightCounts = Object.entries(weightCounts).map(([k,v]) => ({ value: parseFloat(k), coordCount: _.sumBy(v, "coordCount") }));
weightCounts = _.sortBy(weightCounts, v => -v.value);
for (let i = 1; i < weightCounts.length; i++) {
weightCounts[i].coordCount += weightCounts[i-1].coordCount;
}
weightCounts = _.sortBy(weightCounts, v => v.value);
return weightCounts
/*
const arcs = arcIds.map(id => topology.arcs[id]);
return { topology, geometry, arcIds, arcs };
const coords = turf.coordAll(sampleGeoJson);
let weights = coords.map(a => a[2]);
let valueCounts = Object.entries(_.groupBy(weights)).map(e => ({ value: parseFloat(e[0]), count: e[1].length }));
valueCounts = _.sortBy(valueCounts, v => -v.value);

valueCounts.forEach((vc, i) => {
const sum = (i == 0) ? 0 : valueCounts[i-1].total;
vc.total = vc.count + sum;
})
return valueCounts.at(-1);
return valueCounts;
return Plot.plot({
width,
marks: [
Plot.rectY(valueCounts, Plot.binX({y: "count"}, {x: { cumulative: true, thresholds: 200, value: "value"}})),
Plot.ruleY([0])
]
})
*/
}
Insert cell
JSON.stringify(arcStatsMetaFull).replaceAll("\"", "")
Insert cell
arcStatsMetaFull.filter(m => !m.x)
Insert cell
arcStatsMetaFull = {
const allStats = arcStats.flat();
const values = _.sortBy(_.uniq(allStats.map(s => s.value)));
const coordCounts = [];
for (let value of values) {
const stats = allStats.filter(s => s.value >= value);
coordCounts.push({ x: parseFloat(Math.log2(value).toFixed(4)), y: _.sumBy(stats, "coordCount") });
}
return coordCounts;
}
Insert cell
arcStatsMeta = {
const allStats = arcStats.flat();
const values = _.uniq(allStats.map(s => s.value));
const minLogValue = Math.floor(Math.log2(_.min(values)));
const maxLogValue = Math.ceil(Math.log2(_.max(values.filter(v => v < Infinity))));
const coordCounts = [];
for (let value of d3.range(minLogValue, maxLogValue).map(v => 2**v)) {
const stats = allStats.filter(s => s.value >= value);
coordCounts.push({ value, coordCounts: _.sumBy(stats, "coordCount") });
}
return coordCounts;
}
Insert cell
arcStats = {
const topology = sampleTopology;
const arcStats = topology.arcs.map(arc => {
const weightCounts = new Map();
for (let weight of arc.map(a => a[2])) {
weightCounts.set(weight, (weightCounts.get(weight) ?? 0) + 1);
}
return weightCounts;
/*
_.uniq(arc.map(a => a[2]))
let weightCounts = Object.entries(_.groupBy(weights)).map(e => ({ value: parseFloat(e[0]), coordCount: e[1].length }));
weightCounts = _.sortBy(weightCounts, v => -v.value);
/*
for (let i = 1; i < weightCounts.length; i++) {
weightCounts[i].coordCount += weightCounts[i-1].coordCount;
}
weightCounts.push({ value: 0, coordCount: arc.length });
*/
return weightCounts;
});
return arcStats;
}
Insert cell
sumWeightCounts(arcStats)
Insert cell
{
const ts = performance.now();

const topology = sampleTopology;
const arcWeightCounts = topology.arcs.map(arc => {
const weightCounts = new Map();
for (let weight of arc.map(a => a[2])) {
weightCounts.set(weight, (weightCounts.get(weight) ?? 0) + 1);
}
return weightCounts;
});

const result = sumWeightCounts(arcWeightCounts);
const runtime = performance.now() - ts;
return { result, runtime };
}
Insert cell
function sumWeightCounts(weightCounts) {
const sums = new Map();
weightCounts.forEach(wc => {
for (const [weight, count] of wc.entries()) {
sums.set(weight, (sums.get(weight) ?? 0) + count);
}
});
return sums;
}
Insert cell
sampleTopology
Insert cell
sampleGeoJson
Insert cell
function isRing(arc) {
return arc.length > 3 && coordsEqual(arc[0], arc.at(-1));
}
Insert cell
Object.keys(d3.scaleLinear())
Insert cell
{
const traceResult = sampleTraceResult;
const [x0, y0, x1, y1] = traceResult.bbox;
const scaleX = d3.scaleLinear().domain([x0, x1]).range([-180, 180]);
const scaleY = d3.scaleLinear().domain([y0, y1]).range([60, -60]);
debugger;
const scale = {
transform: c => {
c[0] = scaleX(c[0]);
c[1] = scaleY(c[1]);
},
untransform: c => {
c[0] = Math.round(scaleX.invert(c[0])),
c[1] = Math.round(scaleY.invert(c[1]))
}
}
const geojson = _.cloneDeep(await buildGeoJsonRaw(traceResult));
turf.coordEach(geojson, c => scale.transform(c));
// turf.coordEach(geojson, c => scale.untransform(c));
// const untransformed = transformed.map(c => scale.untransform(c));
const stitched = d3.geoStitch(geojson);
turf.coordEach(stitched, c => scale.untransform(c));
return { stitched: stitched.features.filter(f => {
const coords = turf.coordAll(f);
return (_.min(coords.map(c => c[0])) < 10 && _.max(coords.map(c => c[0])) > 2040)
}) };
}
Insert cell
async function buildTopology(traceResult, options) {
options = Object.assign({ wrapX: false, smooth: true, allowExpansion: true }, options);

/*
const edges = {
type: "Feature",
geometry: { type: "MultiLineString" },
properties: { id: NaN }
}
{
let bboxEdges = Bbox.edges(traceResult.bbox);
if (options.wrapX) bboxEdges = _.pick(bboxEdges, ["N", "S"]);
edges.geometry.coordinates = Object.values(bboxEdges);
}
*/
// Create topology
const geojson = await buildGeoJsonRaw(traceResult);
// geojson.features.push(edges);
let topology = topojson.topology({traced: geojson});
topology.bbox = [...traceResult.bbox];
// topology.arcs = topology.arcs.map(a => a.map(c => [...c]));
// Separate ring and line arcs
const ringArcs = [];
const lineArcs = [];
topology.arcs.forEach(arc => (isRing(arc)) ? ringArcs.push(arc) : lineArcs.push(arc));

// Add endpoints of line arcs to coordTypes as junctions
const coordTypes = traceResult.coordTypes.clone();
for (let arc of lineArcs) {
coordTypes.set(arc[0], "junction");
coordTypes.set(arc.at(-1), "junction");
}
// Rotate ring arcs to start on a junction if possible, or the closest point to the polygon center otherwise
const fnFindNewStart = (options.allowExpansion) ? (c => coordTypes.get(c) == "junction") : (c => !!coordTypes.get(c))
for (let arc of ringArcs) {
let newStart = fnFindNewStart(arc);
if (newStart < 0) {
if (Math.abs(d3.polygonArea(arc)) == 1) continue;
const centroid = d3.polygonCentroid(arc);
const distancesSquared = arc.map(c => (centroid[0] - c[0])**2 + (centroid[1] - c[1])**2);
const min = _.min(distancesSquared);
newStart = distancesSquared.indexOf(min);
}
if (newStart > 0) rotateRing(arc, newStart);
}

// Smooth topology
// if (options.smooth) smoothTopology(topology, coordTypes, options.allowExpansion);
// Build topology.properties
topology.properties = {
options,
coordTypes
};

nodeEdges(topology);

/*
// Remove unneeded points
topology = presimplifyProtectJunctions(topology);
topology.arcs = topology.arcs.map(arc => arc.filter(c => c[2] !== 0));
*/
return topology;
}
Insert cell
function nodeEdges(topology) {
const yValueSet = new Set();
const edgeArcs = new Set();
const x0 = topology.bbox[0];
const x1 = topology.bbox[2];
for (let i = 0; i < topology.arcs.length; i++) {
const arc = topology.arcs[i];
for (let [x, y] of arc) {
if (x == x0 || x == x1) {
yValueSet.add(y);
edgeArcs.add(arc);
}
}
}
const yValues = _.sortBy([...yValueSet]);

function getIntermediateValues(fromY, toY) {
if (fromY > toY) return getIntermediateValues(toY, fromY).toReversed();
return yValues.filter(y => y > fromY && y < toY);
}

for (let arc of edgeArcs) {
let oldArc = [...arc];
for (let i = 0; i < arc.length-1; i++) {
const [fromX, fromY] = arc[i];
if (fromX != x0 && fromX != x1) continue;
const [toX, toY] = arc[i+1];
if (toX != x0 && toX != x1) continue;
if (fromX != toX || fromY == toY) continue;

const newValues = getIntermediateValues(fromY, toY).map(y => [fromX, y]);
if (!newValues.length) continue;
arc.splice(i+1, 0, ...newValues);
}
}
}
Insert cell
smoothTopology = {
function coordsEqual(c1, c2) {
return (c1[0] == c2[0] && c1[1] == c2[1]);
}
function maybePush(arc, coord) {
const prevCoord = arc.at(-1);
if (!prevCoord || !coordsEqual(prevCoord, coord)) arc.push(coord);
}

function getMaxShiftAmount(coordType, allowExpansion) {
if (!coordType) return Infinity;
if (!allowExpansion) return 0;
if (coordType == "corner" || coordType == "intrusion") return 0.25;
return 0;
}
function smoothArc(arc, coordTypes, allowExpansion) {
const arcCoordTypes = arc.map(c => coordTypes.get(c));
const smoothArc = [];

function getSegmentToNext(fromCoordIndex) {
const fromCoord = arc[fromCoordIndex];
if (!fromCoord) return;
const toCoord = arc[fromCoordIndex + 1];
if (!toCoord) return;

const axis = (fromCoord[0] != toCoord[0]) ? 0 : 1;
return {
fromCoordIndex, axis,
delta: toCoord[axis] - fromCoord[axis]
}
}
let coord, coordType,
prevSegment, nextSegment,
axis, delta, maxShiftAmount, shiftAmount;
for (let i = 0; i < arc.length; i++) {
coord = arc[i];
coordType = arcCoordTypes[i];
const maxShiftAmount = getMaxShiftAmount(coordType, allowExpansion);

if (!prevSegment || prevSegment.fromCoordIndex != i-1) {
prevSegment = getSegmentToNext(i-1);
}
if (prevSegment) {
let { delta, axis } = prevSegment;
delta *= -1;
shiftAmount = Math.sign(delta) * Math.min(maxShiftAmount, Math.abs(delta)/2);
const newCoord = [coord[0], coord[1]];
newCoord[axis] += shiftAmount;
maybePush(smoothArc, newCoord);
}

nextSegment = getSegmentToNext(i);
if (nextSegment) {
let { delta, axis } = nextSegment;
shiftAmount = Math.sign(delta) * Math.min(maxShiftAmount, Math.abs(delta)/2);
const newCoord = [coord[0], coord[1]];
newCoord[axis] += shiftAmount;
maybePush(smoothArc, newCoord);
}

prevSegment = nextSegment;
}
const first = arc[0];
const last = arc.at(-1);
if (first[0] == last[0] && first[1] == last[1]) {
maybePush(smoothArc, [...smoothArc[0]]);
}
return smoothArc;
}
function smoothTopology(topology, coordTypes, allowExpansion = true) {
// If any coord has a decimal part, don't smooth
for (let arc of topology.arcs) {
for (let coord of arc) {
if (~~coord[0] != coord[0] || ~~coord[1] != coord[1]) throw "topology has already been smoothed"
}
}
// Smooth
topology.arcs = topology.arcs.map(a => smoothArc(a, coordTypes, allowExpansion));
}

smoothTopology.roundFactor = 4;
return smoothTopology;
}
Insert cell
Insert cell
Insert cell
sampleTopology
Insert cell
geoContiguous.newMultiPolygons.filter(p => p.properties.old)
Insert cell
geoContiguous = makeContiguous(sampleTopology, sampleTopology.objects.traced);
Insert cell
function makeContiguous(topology, layer) {
const geoJson = topojson.feature(topology, layer);
const x0 = topology.bbox[0];
const x1 = topology.bbox[2];
const multiPolygons = geoJson.features.filter(f => f.geometry.type == "MultiPolygon");
const newMultiPolygons = [];
for (let feature of multiPolygons) {
const groups = { left: [], right: [], both: [] };
for (let child of feature.geometry.coordinates.map(c => turf.polygon(c, {...feature.properties}))) {
const bbox = turf.bbox(child);
const isLeft = bbox[0] == x0;
const isRight = bbox[2] == x1;
if (isLeft && isRight) groups.both.push(child);
else if (isLeft) groups.left.push(child);
else if (isRight) groups.right.push(child);
}

let didMerge = false;
const newPolygons = [];
if (groups.both.length) {
didMerge = true;
}

if (groups.left.length * groups.right.length > 0) {
didMerge = true;
const jstsRight = groups.right.map(p => jstsReader.read(p).geometry.buffer(0));
const jstsLeft = groups.left.map(p => jstsReader.read(p).geometry.buffer(0));
const jstsShifted = jstsLeft.map(p => jsts.translate(p, [x1, 0]));

let union = jstsRight[0];
jstsRight.slice(1).forEach(p => union = union.union(p));
jstsShifted.forEach(p => union = union.union(p));

const newPolygons = [];
for (let i = 0; i < union.getNumGeometries(); i++) {
let p = union.getGeometryN(i);
const envelope = p.getEnvelopeInternal();
if (envelope.getMinX() >= x1) p = jsts.translate(p, [-x1, 0]);
newPolygons.push(p);
}
const newMultiPolygon = jstsFactory.createMultiPolygon(newPolygons);
newMultiPolygons.push(turf.feature(jstsWriter.write(newMultiPolygon), {...feature.properties, modified: true, old: feature }));
/*
newPolygons.push(jstsRight);
for (let child of groups.left) {
debugger;
const jstsShifted = jsts.translate(jstsReader.read(child).geometry, [x1, 0]);
const polygonToKeep = (jstsRight.intersects(jstsShifted)) ? jstsShifted : jstsReader.read(child).geometry;
if (polygonToKeep == jstsShifted) needsMerge = true;;
newPolygons.push(polygonToKeep);
}
*/
}

if (!didMerge) {
newMultiPolygons.push(feature);
}
// const bbox = turf.bbox(f);
// return bbox[0] <= topology.bbox[0] && bbox[1] >= topology.bbox[1];
// })
}
return { multiPolygons, newMultiPolygons };
}
//turf.coordAll(geoJson)
Insert cell
function _makeContiguous(topology, layer) {
const geoJson = topojson.feature(topology, layer);
const x0 = topology.bbox[0];
const x1 = topology.bbox[2];
const multiPolygons = geoJson.features.filter(f => f.geometry.type == "MultiPolygon");
for (let feature of multiPolygons) {
const groups = { left: [], right: [], both: [] };
for (let child of feature.geometry.coordinates.map(c => turf.polygon(c, {...feature.properties}))) {
const bbox = turf.bbox(child);
const isLeft = bbox[0] == x0;
const isRight = bbox[2] == x1;
if (isLeft && isRight) groups.both.push(child);
else if (isLeft) groups.left.push(child);
else if (isRight) groups.right.push(child);
}

let needsMerge = false;
const newPolygons = [];
if (groups.both.length) {
needsMerge = true;
}

if (groups.left.length * groups.right.length > 0) {
const jstsRight = jstsReader.read(turf.multiPolygon(groups.right.map(p => p.geometry.coordinates))).geometry;
newPolygons.push(jstsRight);
for (let child of groups.left) {
debugger;
const jstsShifted = jsts.translate(jstsReader.read(child).geometry, [x1, 0]);
const polygonToKeep = (jstsRight.intersects(jstsShifted)) ? jstsShifted : jstsReader.read(child).geometry;
if (polygonToKeep == jstsShifted) needsMerge = true;;
newPolygons.push(polygonToKeep);
}
}
else {
continue;
}

if (needsMerge) {
let result = newPolygons[0];
debugger;
newPolygons.slice(1).forEach(p => result = result.union(p));
return result;
}
// const bbox = turf.bbox(f);
// return bbox[0] <= topology.bbox[0] && bbox[1] >= topology.bbox[1];
// })
}
return;
}
//turf.coordAll(geoJson)
Insert cell
jsts.valid.IsValidOp
Insert cell
Insert cell
Insert cell
li = new LoadIndicator(sampleImage);
Insert cell
li.canvas
Insert cell
Insert cell
Insert cell
Insert cell
{
const sourceColorNum = ColorMask.fromArray([10, 20, 30, 83]);
const obj = GpuColorMask.toObject(sourceColorNum);
const obj2 = _.cloneDeep(obj);

obj2.colorNum = ColorMask.fromArray([...ColorMask.toArray(obj2.colorNum).slice(0,3), 6]);
obj2.neighbors.e = true;
obj2.coordType = "junction";
const reverse = GpuColorMask.fromObject(obj2);
const rereverse = GpuColorMask.toObject(reverse);
return { sourceColorNum, obj, reverse, rereverse, finalColor: ColorMask.toObject(rereverse.colorNum) }
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const m1 = new MaskMap(CoordMask);
m1.set([1,1], 1);
const m2 = m1.clone();
m2.set([3,3], 3);
return {m1, m2, eq: [...m1.keys()][0] == [...m2.keys()][0] };
}
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
Bbox.fromDims({x: 5, y: 0, width: 105, height: 110})
Insert cell
class Bbox {
static isEqual(bbox1, bbox2) {
if (bbox1 == bbox2) return true;
return (bbox1?.length == 4 &&
bbox1?.length == 4 &&
bbox1[0] == bbox2[0] &&
bbox1[1] == bbox2[1] &&
bbox1[2] == bbox2[2] &&
bbox1[3] == bbox2[3]);
}
static contains(bbox1, bbox2) {
if (bbox1 == bbox2) return false;
return (bbox1?.length === bbox2?.length &&
bbox1[0] < bbox2[0] &&
bbox1[1] < bbox2[1] &&
bbox1[2] > bbox2[2] &&
bbox1[3] > bbox2[3]);
}
static covers(bbox1, bbox2) {
if (bbox1 == bbox2) return true;
return (bbox1?.length === bbox2?.length &&
bbox1[0] <= bbox2[0] &&
bbox1[1] <= bbox2[1] &&
bbox1[2] >= bbox2[2] &&
bbox1[3] >= bbox2[3]);
}
static containsPoint(bbox, point) {
return Bbox.contains(bbox, [...point, ...point]);
}
static coversPoint(bbox, point) {
return Bbox.covers(bbox, [...point, ...point]);
}
static isPointOnEdge(bbox, point) {
return bbox[0] == point[0] ||
bbox[1] == point[1] ||
bbox[2] == point[0] ||
bbox[3] == point[1];
}
static width(bbox) {
return bbox[2] - bbox[0] + 1;
}
static height(bbox) {
return bbox[3] - bbox[1] + 1;
}
static dims(bbox) {
return { x: bbox[0], y: bbox[1], width: Bbox.width(bbox), height: Bbox.height(bbox) };
}
static fromDims(dims) {
return [
dims.x,
dims.y,
dims.width + dims.x - 1,
dims.height + dims.y - 1
];
}
static fromPoints(points) {
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (let [x, y] of points) {
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
return [x0, y0, x1, y1];
}
static corners(bbox) {
const [x0, y0, x1, y1] = bbox;
return [ [x0,y0], [x0,y1], [x1,y1], [x1,y0] ];
}
static edges(bbox) {
const [x0, y0, x1, y1] = bbox;
return {
N: [[x0,y0], [x1,y0]],
E: [[x1,y0], [x1,y1]],
S: [[x0,y1], [x1,y1]],
W: [[x0,y0], [x0,y1]]
}
}
static area(bbox) {
return Bbox.width(bbox) * Bbox.height(bbox);
}
static buffer(bbox, amount) {
return [bbox[0] - amount, bbox[1] - amount, bbox[2] + amount, bbox[3] + amount];
}
}
Insert cell
async function pause(timeout = 10) {
await new Promise(r => requestIdleCallback(r, { timeout }));
}
Insert cell
pauseIfNeeded = {
const pauseIncrement = 1000/15;
let lastPause = performance.now();

async function pauseIfNeeded(_pauseIncrement) {
_pauseIncrement ??= pauseIncrement;
if (lastPause + _pauseIncrement < performance.now()) {
await new Promise(r => requestAnimationFrame(r));
lastPause = performance.now();
}
}

return pauseIfNeeded;
}
Insert cell
Insert cell
viewof highlightSyntax = Inputs.toggle({ label: "GLSL syntax highlighting" })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
topojson = require("topojson-client", "topojson-simplify", "topojson-server")
Insert cell
Insert cell
idb = await import('https://unpkg.com/idb@7.1.1/with-async-ittr.js?module')
Insert cell
Insert cell
Insert cell
import { mapInfo as worldMapInfo } from "2e90991e4c98c8a7"
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