Public
Edited
Nov 4, 2023
Fork of DeckGL
Insert cell
Insert cell
container = html `<div style="height:450px;"></div>`
Insert cell
deckgl = {
const deckgl = new deck.DeckGL({
views: new deck.MapView({repeat: true}),
container,
controller: true,
initialViewState: {longitude: 0, latitude: 35, zoom: 1},
layers: [new FeaturesLayer({
id: 'countries',
data: countries.features
})]
});
invalidation.then(() => {
try {
deckgl.finalize();
}
catch (e) {
console.log('error finalizing deckgl instance', e);
}
container.innerHTML = "";
});
return deckgl;
}
Insert cell
fl = deckgl.layerManager.getLayers()[0].getSubLayers()[0]
Insert cell
Object.keys(deck.GeoJsonLayer)
Insert cell
Object.getPrototypeOf(fl).layerName
Insert cell
fl.props.id
Insert cell
fl.getSubLayers()[0] instanceof deck.SolidPolygonLayer // .PathLayer
Insert cell
deck.PathStyleExtension.defaultProps
Insert cell
class InnerBorderExtension extends deck.PathStyleExtension {
static {
InnerBorderExtension.layerName = "InnerBorderExtension";
InnerBorderExtension.defaultProps = {}
}
}
Insert cell
class PPGeoJsonLayer extends deck.GeoJsonLayer {
static {
PPGeoJsonLayer.layerName = "PPGeoJsonLayer";
}
renderLayers() {
const layers = super.renderLayers();
const ppEffect = new deck.PostProcessEffect(luma.shadertools.brightnessContrast, { brightness: 1, contrast: 1 });
const pathLayers = layers.filter(l => l instanceof deck.PathLayer);
pathLayers.forEach(p => p.updateState({ effects: [ppEffect] }));
return layers;
}
}
Insert cell
Insert cell
class FeaturesLayer extends deck.CompositeLayer {
static {
FeaturesLayer.layerName = "FeaturesLayer";
FeaturesLayer.defaultProps = Object.assign({}, deck.GeoJsonLayer.defaultProps, {
stroked: true,
filled: true,
extruded: false,
wrapLongitude: true,
autoHighlight: true,
lineWidthUnits: 'common',
lineJointRounded: true,
lineWidthMinPixels: 1,
lineWidthMaxPixels: 10,
getLineWidth: 1,
getFillColor: f => [128, 128, 128],
getLineColor: f => [255, 0, 0],
getOffset: f => 0.5,
extensions: [new deck.PathStyleExtension({offset: true})],
getFeatureId: f => f.properties.name
});
}
getForwardedProps() {
const forwarded = { updateTriggers: {} };
for (let key of Object.keys(deck.GeoJsonLayer.defaultProps)) {
if (key in this.props) forwarded[key] = this.props[key];
if (key in this.props.updateTriggers) forwarded.updateTriggers[key] = this.props.updateTriggers[key];
}
return forwarded;
}
renderLayers() {
const layers = [];
for (let feature of this.props.data ?? []) {
const id = this.props.getFeatureId(feature);
const layer = new deck.GeoJsonLayer(this.getForwardedProps(), this.getSubLayerProps({ id }), { data: feature });
layers.push(layer);
}
return layers;
}
}
Insert cell
geoJsonLayer = new deck.GeoJsonLayer({
id: 'countries',
data,
stroked: true,
filled: true,
extruded: false,
wrapLongitude: true,
autoHighlight: true,

lineWidthUnits: 'common',
lineJointRounded: true,
lineWidthMinPixels: 1,
lineWidthMaxPixels: 10,
getLineWidth: 0.75,
getFillColor: [0,0,0,128],
getLineColor: f => [0, 0, 0, 0],
getOffset: f => 0.5,

extensions: [new deck.PathStyleExtension({offset: true})],
pickable: true
});
Insert cell
countries = fetch('https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_admin_0_scale_rank.geojson')
.then(resp => resp.json())
.then(geo => d3.geoStitch(geo))
.then(result => {
// Join results
const countries = new Map();
for (const feature of result.features) {
const name = feature.properties.sr_subunit;
if (!countries.has(name)) countries.set(name, []);
countries.get(name).push(feature);
}
const features = Array.from(countries.entries())
.map(([name, features], i) => {
// const color = [55 + i / 200, 55 + i % 200, 128];
return (features.length === 1)
? turf.polygon(features[0].geometry.coordinates, {name})
: turf.multiPolygon(features.map(f => f.geometry.coordinates), {name});
// turf.coordEach(feature, c => c[2] = i);
});
const fc = turf.featureCollection(features);
return d3.geoStitch(fc);
});
Insert cell
Insert cell
Insert cell
deck.PostProcessEffect
Insert cell
Insert cell
jf = countryRanks[0].geom
Insert cell
jf.getGeometryType();
Insert cell
_.groupBy(countryRanks.flatMap(r => r.distances))
Insert cell
transform = jsts.Transformation.translationInstance(360, 0);
Insert cell
{
const [,b1y1,,b1y2] = [1,2,3,4];
return { b1y1, b1y2 }
}
Insert cell
countryRanks = {
function getBbox(coords) {
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (let [x, y] of coords) {
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];
}
function getBboxes(geometry) {
let bboxes;
if (geometry.type == "MultiPolygon") {
bboxes = geometry.coordinates.map(p => getBbox(p[0]));
}
if (geometry.type == "Polygon") {
bboxes = [getBbox(geometry.coordinates[0])];
}
bboxes.forEach(b => b.area = Bbox.area(b));
bboxes.sort((a,b) => b.area - a.area);
return bboxes;
}
function getDistance(bboxes1, bboxes2) {
let minDistance = Infinity;
for (let bbox1 of bboxes1) {
for (let bbox2 of bboxes2) {
const distance = Bbox.getDistance(bbox1, bbox2, [360]);
if (distance == 0) return 0;
if (distance < minDistance) minDistance = distance;
}
}
return minDistance;
}
const countryInfos = [];
for (let i = 0; i < countries_nostitch.length; i++) {
const feature = countries_nostitch[i];
const bboxes = getBboxes(feature.geometry);
const area = bboxes.reduce((sum, b) => sum + b.area, 0);
countryInfos.push({ feature, bboxes, area, distances: [] });
}
for (let i = 0; i < countryInfos.length; i++) {
const bboxes_i = countryInfos[i].bboxes;
for (let j = i; j < countryInfos.length; j++) {
const bboxes_j = countryInfos[j].bboxes;
const distance = getDistance(bboxes_i, bboxes_j);
countryInfos[i].distances[j] = countryInfos[j].distances[i] = distance;
}
}

for (let countryInfo of countryInfos) {
countryInfo.touchCount = countryInfo.distances.filter(d => d < 15).length - 1;
}
return _.sortBy(countryInfos, c => -c.touchCount);
}
Insert cell
Bbox.getDistance([180,5,354,178], [0,0,2,2], [360, 180]);
Insert cell
Insert cell
Bbox = {
const RANGE_LESS_THAN = 1;
const RANGE_OVERLAPS = 0;
const RANGE_GREATER_THAN = -1;
function rangeCompare([min, max], value) {
if (value < min) return RANGE_LESS_THAN;
if (value > max) return RANGE_GREATER_THAN;
return RANGE_OVERLAPS;
}
function rangesDistance(range1, range2, wrapDistance) {
const compare1 = rangeCompare(range1, range2[0]);
const compare2 = rangeCompare(range1, range2[1]);
let compareResult = RANGE_OVERLAPS;
if (compare1 == compare2) compareResult = compare1;
if (compareResult == RANGE_OVERLAPS) return 0;

const lowRange = (compareResult == RANGE_GREATER_THAN) ? range1 : range2;
const highRange = (lowRange == range2) ? range1 : range2;
const distance = highRange[0] - lowRange[1];
if (!wrapDistance) return distance;

const lowRangeWrapped = [lowRange[0] + wrapDistance, lowRange[1] + wrapDistance];
const wrappedDistance = lowRangeWrapped[0] - highRange[1];
return Math.min(distance, wrappedDistance);
}
function getDistanceVector(bbox1, bbox2, worldSize) {
const [worldSizeX, worldSizeY] = [...(worldSize ?? []), 0, 0];
const dx = rangesDistance([bbox1[0], bbox1[2]], [bbox2[0], bbox2[2]], worldSizeX);
const dy = rangesDistance([bbox1[1], bbox1[3]], [bbox2[1], bbox2[3]], worldSizeY);
return [dx, dy];
}
class Bbox {
static getDistance(bbox1, bbox2, worldSize) {
const [dx, dy] = getDistanceVector(bbox1, bbox2, worldSize);
if (dx == 0) return dy;
if (dy == 0) return dx;
return Math.sqrt(dx**2 + dy**2);
}
static intersects(bbox1, bbox2, worldSize) {
const [dx, dy] = getDistanceVector(bbox1, bbox2, worldSize);
return dx * dy == 0;
}
static width(bbox) {
return bbox[2] - bbox[0];
}
static height(bbox) {
return bbox[3] - bbox[1];
}
static area(bbox) {
return Bbox.width(bbox) * Bbox.height(bbox);
}
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];
}
}
return Bbox;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
jsts = {
const jsts = await require('jsts@2.11/dist/jsts.min.js');
const result = {
...jsts.geom,
Orientation: jsts.algorithm.Orientation,
DistanceOp: jsts.operation.distance.DistanceOp,
ConvexHull: jsts.algorithm.ConvexHull,
Simplifier: jsts.simplify.DouglasPeuckerSimplifier,
Transformation: jsts.geom.util.AffineTransformation
};
delete result.util;
const factory = new jsts.geom.GeometryFactory(new jsts.geom.PrecisionModel(), -1); // Integer precision, SRID -1
const reader = new jsts.io.GeoJSONReader(factory);
const writer = new jsts.io.GeoJSONWriter(factory);

result.factory = factory;
result.parse = g => reader.read(g);
result.stringify = g => writer.write(g);
return result;
}
Insert cell
Insert cell
martinez = await import('https://cdn.skypack.dev/martinez-polygon-clipping@0.7.3?min')
Insert cell
offset = (await import('https://cdn.skypack.dev/polygon-offset@0.3.2?min')).default
Insert cell
countries_nostitch[0].geometry
Insert cell
polyUtil.offset(countries_nostitch[0].geometry.coordinates, 1)
Insert cell
polyUtil = {
const { diff, intersection, union, xor } = await import('https://cdn.skypack.dev/martinez-polygon-clipping@0.7.3?min');
const OffsetModule = (await import('https://cdn.skypack.dev/polygon-offset@0.3.2?min')).default
const offsetInstance = new OffsetModule();

function offset(points, distance) {
return offsetInstance.data(points).offset(distance);
}
return { diff, intersection, union, xor, offset };
/*
var margined = offset.data(points).margin(10);
var padding = offset.data(points).padding(10);
// decides from the sign of x: negative for padding
var unknown = offset.data(points).arcSegments(3).offset(x);
*/
}
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