Public
Edited
Oct 16, 2023
Fork of Leaflet POC
Insert cell
Insert cell
// worldCopyJump
Insert cell
{
// booleanClockwise
let rect, rewound;
rect = turf.polygon([[[70, -10], [90, -10], [90, 10], [70, 10], [70, -10]].toReversed()]);
rect = turf.polygon([[[170, -10], [-170, -10], [-170, 10], [170, 10], [170, -10]].toReversed()]);
return {
rect,
isClockwise: turf.booleanClockwise(rect.geometry.coordinates[0]),
};
}
Insert cell
{
const viewbox = new L.LatLngBounds([[-10,20],[10,-20]]);
Insert cell
function getSearchBounds(leafletBounds) {
return leafletBounds
}
Insert cell
{
const layers = [];
map.map.eachLayer(l => layers.push(l));
return layers;
}
Insert cell
m = new L.Map(DOM.element("div"));
Insert cell
countries
Insert cell
llb = map.map.getBounds()
Insert cell
L
Insert cell
map.map.options.crs
Insert cell
map.map
Insert cell
worldBounds = {
const proj = map.map.options.crs.projection;
return L.latLngBounds(proj.unproject(proj.bounds.min), proj.unproject(proj.bounds.max))
}
Insert cell
{
const worldWidth = worldBounds.getEast() - worldBounds.getWest();
const worldBbox = worldBounds.toLngLatArray();

const visibleBounds = map.map.getBounds();
const w = visibleBounds.getWest();
const e = visibleBounds.getEast();
const minOffset = Math.floor((w - worldBounds.getWest()) / worldWidth);
const maxOffset = Math.floor((e - worldBounds.getWest()) / worldWidth);

const llbPanels = []
for (let offset = minOffset; offset <= maxOffset; offset++) {
}
return { w, minOffset, e, maxOffset };
}
Insert cell
map = {
const height = width * mapSize.height / mapSize.width;
const container = html`<div style="height:${height}px;">`;
yield container;

// const corner1 = L.latLng(-90, -180)
// const corner2 = L.latLng(90, 180)
// const maxBounds = L.latLngBounds(corner1, corner2)


const map = L.map(container, {
zoomAnimation: false,
zoomSnap: 0,
topoMove: true,
// maxBounds: [[-90, -Infinity], [90, Infinity]],
worldCopyJump: true
});
// const topoLayer = new TopoJsonFeatureGroup(countries, countries.objects.countries, { padding: 1 });
// topoLayer.addTo(map);
const geoLayer = new L.GeoJSON(countriesRaw);
geoLayer.addTo(map);
// const bounds = L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180));
// map.fitBounds(bounds);

map.setMaxBounds([[-90, -360], [90, 360]])
function fitWorld() {
const zoomY = map.getBoundsZoom(L.latLngBounds(L.latLng(-90, 0), L.latLng(90, 0)));
const zoomX = map.getBoundsZoom(L.latLngBounds(L.latLng(0, -180), L.latLng(0, 180)));
map.setMinZoom(Math.max(zoomX, zoomY));
}
fitWorld();
map.fitBounds([[-90, -360], [90, 360]]);
container.map = map;
yield container;
}
Insert cell
countriesRaw = {
const geojson = await fetch("https://cdn.jsdelivr.net/gh/martynafford/natural-earth-geojson/50m/cultural/ne_50m_admin_0_countries_lakes.json").then(r => r.json());
for (let c of geojson.features) {
delete c.properties;
}
return geojson;
}
Insert cell
countries = topojson.topology({ countries: countriesRaw });
Insert cell
[
L.Map.prototype.options.crs.wrapLng,
L.Map.prototype.options.crs.wrapLat
]
Insert cell
L.Map.prototype.options.crs.latLngToPoint(L.latLng(0,180), -8)
Insert cell
L.Map.prototype.options.crs.zoom(1)
Insert cell
getCrsFromD3Projection(d3.geoMiller()).toString()
Insert cell
L.Map.prototype.options.crs
Insert cell
0.5 / (Math.PI * L.Projection.SphericalMercator.R);
Insert cell
Object.keys(gm)
Insert cell
[
d3.geoMiller()([0, 0]),
d3.geoMiller().invert([0,0])
]
Insert cell
L.Map.prototype.options.crs.unproject(L.point(-179,0))
Insert cell
buildCRS(d3.geoMiller()).unproject(L.point(-179,0))
Insert cell
d3.geoMiller().fitWidth(100000, { type: "Sphere" }).translate()
Insert cell
function buildCRS(d3Projection) {
const R = L.CRS.Earth.R;
d3Projection = (d3Projection.invert) ? d3Projection : d3Projection();
d3Projection = d3Projection.fitWidth(2 * Math.PI * R, { type: "Sphere" });

const [halfWidth, halfHeight] = d3Projection([180,-90]).map(px => px / 2);
const rawBounds = [[-halfWidth, -halfHeight], [halfWidth, halfHeight]];
d3Projection = d3Projection.fitExtent(rawBounds, { type: "Sphere" });
console.log(d3Projection.scale())
console.log(d3Projection.translate())
console.log(d3Projection([0,0]))
console.log(d3Projection([180,-90]))
console.log(d3Projection([-180,90]))
return L.Util.extend({}, L.CRS.Earth, {
code: -1,
projection: {
project(latlng) {
const projected = d3Projection([latlng.lng, latlng.lat]);
return new L.Point(projected[1], projected[0]);
},
unproject(point) {
const inverted = d3Projection.invert([point.x, point.y]);
return new L.LatLng(inverted[1], inverted[0]);
},
bounds: L.bounds(rawBounds)
},
transformation: L.transformation(0.5/halfWidth, 0.5, -0.5/halfHeight, 0.5)
});
}
Insert cell
ps = presimplifyTopoJson(countries)
Insert cell
_.max(ps.arcs.flat().filter(c => c[2] > 0 && c[2] < Infinity).map(c => c[1]))
Insert cell
function presimplifyTopoJson(topology, crs) {
crs ??= L.Map.prototype.options.crs;
let zoom = crs.zoom(1);
let presimplified = {...topology};
presimplified.arcs = topology.arcs.map(arc => {
return arc.map(([lng,lat]) => {
const { x, y } = crs.latLngToPoint(L.latLng(lat, lng), zoom);
return [x, y];
});
});
presimplified = topojson.presimplify(presimplified);

let srcArc, srcCoord;
presimplified.arcs.forEach((arc, i) => {
srcArc = topology.arcs[i];
arc.forEach((c,j) => {
srcCoord = srcArc[j];
c[0] = srcCoord[0];
c[1] = srcCoord[1];
if (srcCoord[2] === Infinity || srcCoord[2] === 0) c[2] = srcCoord[2];
})
})
return presimplified;
}
Insert cell
topojson.presimplify
Insert cell
L.CRS.Earth
Insert cell
turf.featureCollection()
Insert cell
sp = d3.scalePow()
.exponent(2)
.domain([0, 1, 3])
.range([0.5, 0.5, 6]);
Insert cell
sp(3)
Insert cell
map.getrend
Insert cell
rtLayer = {
const scale = d3.scalePow()
.exponent(2)
.domain([0, 1, 5])
.range([1, 1, 10]);
return L.realtime(turf.featureCollection([]), {
noClip: true,
smoothFactor: 1,
weight: 1,
fnWeight(lineWidth, renderer) {
return scale(renderer._zoom) * lineWidth;
}
});
}
Insert cell
rtLayer.options
Insert cell
rtLayer.update(geoJsonSlice)
Insert cell
mutable mapState = ({});
Insert cell
function onMove(e) {
mutable mapState = {
e,
zoom: e.target?.getZoom(),
bounds: e.target?.getBounds()
};
}
Insert cell
Insert cell
Insert cell
clippedSVG = new ClippedSVG()
Insert cell
Insert cell
Insert cell
geoJson.features

Insert cell
geoJsonSlice = {
let index = 0;
let slice;
const features = geoJson.features;
while (true) {
if (index > features.length) index = 0;
index++;
yield turf.featureCollection(features.slice(index, index + 20));
await Promises.delay(1000);
}
}
Insert cell
Insert cell
geoJson
Insert cell
L.geoJSON({ type: "FeatureCollection", features: [testFeature] })
Insert cell
L.CRS.Simple.projection.bounds
Insert cell
wrappingCrs.infinite
Insert cell
mapSize = ({ width: 2048, height: 904 });
Insert cell
Insert cell
topoJson
Insert cell
Insert cell
geoJson
Insert cell
topoJson
Insert cell
contigTopology =
return makeContiguous(st2, st2.objects.traced);
}
Insert cell
Bbox.toString()
Insert cell
topojson.feature(topoJson, topoJson.objects.traced)
Insert cell
topoJson.objects
Insert cell
topoJson.objects.traced.geometries[0]
Insert cell
topojson.feature(topoJson, topoJson.objects.traced.geometries[0])
Insert cell
_.intersection(Object.keys(L.Polygon.prototype), Object.keys(L.Polyline.prototype))
Insert cell
pa = new PolygonArc(topoJson, topoJson.objects.traced.geometries[0]);
Insert cell
pa._map
Insert cell
polyPathOverrides = ({
getGeometry() {
return this._geometry;
},
setGeometry(geometry) {
this._setGeometry({ type: geometry?.type, arcs: geometry?.arcs });
// todo: Register arcs
return this.redraw();
},
setLatLngs(latlngs) {
throw "Can't call setLatLngs on a topology path.";
},
setFeatureGroup(topoJsonFeatureGroup) {
this._topoJsonFeatureGroup = topoJsonFeatureGroup;
// todo: Register arcs
},
getTsm() {
return this._topoJsonFeatureGroup?._tsm;
},
markDirty() {
this._latlngs = false;
},
isEmpty() {
return !this._geometry?.arcs?.length;
},
_setGeometry(geometry) {
this.markDirty();
this._geometry = geometry;
},
_simplifyPoints() {
return;
}
});
Insert cell
Layers = L.Class.extend({
})
Insert cell
PolylineArc = {
const levelsDeep = {
LineString: 0,
MultiLineString: 1,
Polygon: 1,
MultiPolygon: 2
};
return L.Polyline.extend({
...polyPathOverrides,
options: {
noClip: false
},
initialize(topology, geometry, options) {
L.Util.setOptions(this, options);
this._tsm = undefined;
this._topology = topology;
this._setGeometry({ type: geometry?.type, arcs: geometry?.arcs, properties: geometry?.properties ?? {} });
this.__latlngs = false;
Object.defineProperty(this, "_latlngs", {
get() {
if (this.__latlngs === false && this._tsm) {
if (!this._topology || !this._geometry) return false;
const feature = topojson.feature(this._topology, this._geometry);
const latlngs = L.GeoJSON.coordsToLatLngs(feature.geometry.coordinates, levelsDeep[geometry.type]);
this._setLatLngs(latlngs);
}
return this.__latlngs;
},
set(value) {
this.__latlngs = value;
this.redraw();
},
enumerable: true,
configurable: true
});
},
onAdd(map) {
debugger;
this._tsm = TopoSimplifyManager.getOrCreateInstance(map, this.topology);
L.Path.prototype.onAdd?.call(this, ...arguments);
},
onRemove(map) {
debugger;
this._tsm = undefined;
this.__latlngs === false;
L.Path.prototype.onRemove.call(this, ...arguments);
}
});
}
Insert cell
getLayersById(map.map, [260, 261, 265])
Insert cell
Object.values(map.map._layers).filter(l => l._layers)
Insert cell
function getLayersById(map, stamps) {
const start = performance.now();
stamps = (stamps instanceof Set) ? stamps : new Set(stamps);
const found = [];
map.eachLayer(function(layer) {
if (stamps.has(L.Util.stamp(layer))) {
found.push(layer);
}
});
const runtime = performance.now() - start;
found.unshift(runtime);
return found;
}
Insert cell
{
return;
Map.include({
/* @method eachLayer(fn: Function, context?: Object): this
* Iterates over the layers of the map, optionally specifying context of the iterator function.
* ```
* map.eachLayer(function(layer){
* layer.bindPopup('Hello');
* });
* ```
*/
eachLayer(method, context) {
for (const i in this._layers) {
if (Object.hasOwn(this._layers, i)) {
method.call(context, this._layers[i]);
}
}
return this;
}
});
}
Insert cell
Insert cell
{
const lg = new L.LayerGroup();
return lg;
}
Insert cell
TopoJsonFeatureGroup = {
function geometryToLayer(topology, geometry, options) {
const arcs = geometry ? geometry.arcs : null;
if (!arcs && !geometry) return null;
switch (geometry.type) {
case 'LineString':
case 'MultiLineString':
return new PolylineArc(topology, geometry, options);
case 'Polygon':
case 'MultiPolygon':
return new PolygonArc(topology, geometry, options);
case 'GeometryCollection':
const layers = geometry.geometries.map(g => geometryToLayer(g, options)).filter(g => g);
return new L.FeatureGroup(layers);
default:
throw new Error('Invalid/unsupported GeoJSON object.');
}
}

return L.FeatureGroup.extend({
initialize(topology, object, options) {
L.Util.setOptions(this, options);
this._topology = topology;
this._layers = {};
if (object) this.addData(object);
},
addData(topojson) {
let geometries = Array.isArray(topojson) ? topojson : topojson.geometries;

// Array or GeometryCollection: call addData on all individual geometries
if (geometries) {
geometries.filter(g => g.geometries || g.arcs).forEach(g => this.addData(g));
return this;
}

// Geometry
const options = this.options;
if (options.filter && !options.filter(topojson)) return this;
const layer = geometryToLayer(this._topology, topojson, options);
layer.setFeatureGroup(this);
if (!layer) return this;
layer.defaultOptions = layer.options;
this.resetStyle(layer);
if (options.onEachGeometry) options.onEachGeometry(topojson, layer);
return this.addLayer(layer);
},
resetStyle(layer) {
return L.GeoJSON.prototype.resetStyle.call(this, layer);
},
setStyle(style) {
return L.GeoJSON.prototype.setStyle.call(this, style);
},

beforeAdd(map) {
this._tsm = TopoSimplifyManager.getOrCreateInstance(map, this._topology);
},
onRemove(map) {
this._tsm = undefined;
return L.FeatureGroup.prototype.onRemove.call(this, map);
},

_setLayerStyle(style) {
return L.GeoJSON.prototype._setLayerStyle.call(this, style);
}
});
}
Insert cell
topoJson.objects.traced
Insert cell
new TopoJsonFeatureGroup(topoJson, topoJson.objects.traced)
Insert cell
topoJson.objects.traced.geometries[0]
Insert cell
Insert cell
d3 = require("d3", "d3-geo", "d3-geo-projection");
Insert cell
miller = d3.geoMiller()
Insert cell
miller([-180, 0])
Insert cell
TopoSimplifyManager.getInstances()
Insert cell
TopoSimplifyMoveHandler = L.Handler.extend({
addHooks: function() {
L.DomEvent.on(this._map, 'move', this.onMapMove, this);
},

removeHooks: function() {
L.DomEvent.off(this._map, 'move', this.onMapMove, this);
},

onMapMove: function(event) {
const map = event.target;
const size = map.getSize();
const bounds = map.getBounds();
// const bbox = [bounds.getWest(), bounds.getNorth(), bounds.getEast(), bounds.getSouth()];
const bbox = [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()]; // WHY
const pxRatio = Bbox.width(bbox) / size.x;

console.log('onMapMove');
for (let tsm of TopoSimplifyManager.getInstances()) {
tsm.updateArcs(bbox, pxRatio);
}
}
});
Insert cell
map.map.options.crs
Insert cell
tsm = new TopoSimplifyManager(countries, map.map.options.crs);
Insert cell
{
tsm.arcs.acceptDirty();
tsm.updateArcs([-10, 10, 10, -10], 25);
return tsm.asTopology();
}
Insert cell
class TopoSimplifyManager {
static getOrCreateInstance(leafletMap, topology) {
let instancesPerMap = TopoSimplifyManager._instancesPerMap ??= new WeakMap();
let instances = instancesPerMap.get(leafletMap);
if (!instances) {
instances = new Map();
instancesPerMap.set(leafletMap, instances);
}
let instance = instances.get(leafletMap);
if (!instance) {
debugger;
instance = new TopoSimplifyManager(topology, leafletMap.options.crs);
instances.set(leafletMap, topology);
}

return instance;
}
constructor(topology, crs) {
this.crs = crs;
// Transform coordinates to unit values, then presimplify
const zoom = crs.zoom(1);
let presimplified = {...topology};
let lng, lat;
presimplified.arcs = topology.arcs.map(arc => {
return arc.map(([lng,lat]) => {
const { x, y } = crs.latLngToPoint(L.latLng(lat, lng), zoom);
return [x, y];
});
});
presimplified = topojson.presimplify(presimplified);

// Revert to original coordinate system
let srcArc, srcCoord, maxValue = 0;
presimplified.arcs.forEach((arc, i) => {
srcArc = topology.arcs[i];
arc.forEach((c,j) => {
srcCoord = srcArc[j];
c[0] = srcCoord[0];
c[1] = srcCoord[1];
if (srcCoord[2] === Infinity) {
c[2] = srcCoord[2];
}
if (c[2] < Infinity && c[2] > maxValue) {
maxValue = c[2];
}
});
});

// Save
const arcs = this.sourceArcs = presimplified.arcs;
this.maxValue = maxValue;
this.lowResArcs = topojson.simplify(presimplified, 2 * maxValue).arcs;
this.arcs = getTrackedArcArray(this.lowResArcs);

// Build index
const index = this.index = new Flatbush(this.sourceArcs.length);
arcs.forEach(arc => index.add(...Bbox.fromPoints(arc)));
index.finish();
}
getArcCoords(arcId, tolerance) {
if (tolerance == Infinity) {
return this.lowResArcs[arcId];
}
else {
return this.sourceArcs[arcId].filter(c => c[2] >= tolerance).map(c => [c[0], c[1]]);
}
}
updateArcs(bbox, zoom, tolerance = 1) {
bbox = Bbox.fromPoints(Bbox.corners(bbox));
tolerance /= this.crs.scale(zoom);
if (tolerance > this.maxValue) {
this.lowResArcs.forEach((arc, i) => this.arcs[i] = arc);
return;
}
const visibleArcIds = new Set(this.index.search(...bbox));
debugger;
this.sourceArcs.forEach((arc, i) => {
if (visibleArcIds.has(i)) {
this.arcs[i] = this.getArcCoords(i, tolerance);
}
else {
this.arcs[i] = this.lowResArcs[i];
}
});
console.log("updateArcs", visibleArcIds, this.arcs, ...arguments);
}
asTopology() {
return {
type: "Topology",
arcs: this.arcs,
properties: {
dirtyArcIds: this.arcs.getDirtyIndices()
}
}
}
}

Insert cell
html`<link href='${resolve('leaflet@1.2.0/dist/leaflet.css')}' rel='stylesheet' />`
Insert cell
{
const a = getTrackedArray([1,2,3]);
a.splice(1, 0, 2.3, 2.7);
a.push(false);
a.markDirty();
debugger;
return {
a,
dirtyIndices: a.getDirtyIndices()
};
}
Insert cell
a = getTrackedArray([1,2,3])
Insert cell
a.a
Insert cell
Object.getOwnPropertyDescriptor(a, "acceptDirty")
Insert cell
getTrackedArray = {
function asIndex(prop) {
const maybeIndex = Number(prop);
return (maybeIndex >= 0 && Number.isInteger(maybeIndex)) ? maybeIndex : NaN;
}

function clone(array) {
const clone = array.slice(0,0);
clone.length = array.length;
array.forEach((e,i) => clone[i] = array[i]);
return clone;
}

function defaultIsEqual(a,b) {
return a === b;
}

const defaultOptions = { fnIsEqual: defaultIsEqual, inPlace: false };
function createProxy(target, options) {
const { fnIsEqual, inPlace } = Object.assign({}, defaultOptions, options);
if (!inPlace) target = clone(target);

const dirtyIndices = new Set();
target.getDirtyIndices = () => [...dirtyIndices];
target.acceptDirty = () => dirtyIndices.clear();
target.markDirty = () => target.forEach((e,i) => dirtyIndices.add(i));
target.getValues = () => clone(target);

const handler = {
defineProperty(target, key, descriptor) {
const index = asIndex(key);
if (!isNaN(index)) {
const currentValue = target[index];
if (!fnIsEqual(descriptor.value, currentValue)) dirtyIndices.add(index);
}
return Reflect.defineProperty(...arguments);
},
deleteProperty(target, key) {
const index = asIndex(key);
if (!isNaN(index)) {
if (index in target) dirtyIndices.add(index);
}
return Reflect.deleteProperty(...arguments);
}
};

return new Proxy(target, handler);
}

return createProxy;
}
Insert cell
topoArcsEqual = {
function coordsEqual(a, b) {
if (a === b) return true;
return (a.length == b.length && a[0] === b[0] && a[1] === b[1]);
}
function arcsEqual(a, b) {
if (a === b) return true;
// This isn't a heuristic, it's just how topojson works
return (a.length == b.length && coordsEqual(a[0], b[0]) && coordsEqual(a[1], b[1]));
}
return arcsEqual;
}
Insert cell
function getTrackedArcArray(arcs) {
function coordsEqual(a, b) {
if (a === b) return true;
return (a.length == b.length && a[0] === b[0] && a[1] === b[1]);
}
function fnIsEqual(a, b) {
if (a === b) return true;
// This isn't a heuristic, it's just how topojson works
return (a.length == b.length && coordsEqual(a[0], b[0]) && coordsEqual(a[1], b[1]));
}
return getTrackedArray(arcs, { fnIsEqual });
}
Insert cell
Insert cell
Insert cell
Insert cell
turf = await require('@turf/turf@6.5.0/turf.min.js')
Insert cell
svg`<svg viewBox="0 0 100 100">
<defs>
<clipPath id="myClip">
<circle id="circle" cx="40" cy="35" r="35" />
</clipPath>
</defs>
<g transform="scale(1 0.5)">>
<use clip-path="url(#myClip)" href="#circle" fill="red" stroke="blue" stroke-width="10" />
</g>
</svg>`
Insert cell
Insert cell
import { smoothTopology, makeContiguous, sampleTopology as topoJson, Bbox, sampleGeoJson as geoJson } from "8c9dbd3e4bfdb2e9"
Insert cell
topojson = require("topojson-client", "topojson-simplify", "topojson-server")
Insert cell
Flatbush = require('flatbush@4.2.0/flatbush.js');
Insert cell
leafletBase = await require('leaflet@1.2.0');
Insert cell
wvm = {
const wvm = new WeakValueMap();
for (let i = 0; i < 50; i++) {
wvm.set(i, JSON.parse(JSON.stringify(countriesRaw)));
}
return wvm;
}
Insert cell
wvm.get("potato")
Insert cell
WeakValueMap = {
function startCleaning(wvm) {
const ref = new WeakRef(wvm);
debugger;
const interval = setInterval(() => {
const wvm = ref.deref();
if (wvm) {
wvm.clean();
}
else {
console.log("wvm garbage collected");
clearInterval(interval);
return;
}
}, 1000);
}
class WeakValueMap extends Map {
constructor(iterable) {
super(iterable);
if (startCleaning) startCleaning(this);
invalidation.then(() => {
debugger;
});
}
set(key, value) {
return super.set(key, new WeakRef(value));
}
get(key) {
return super.get(key)?.deref();
}
has(key) {
return !!this.get(key);
}
*[Symbol.iterator]() {
let value;
for (let [key, ref] of super[Symbol.iterator]()) {
value = ref.deref();
if (value) yield [key, value];
}
}
clean() {
for (let [key, value] of this) {
if (!value) this.delete(key);
}
}
get size() {
this.clean();
return super.size;
}
}

return WeakValueMap;
}
Insert cell
// Jiggery-pokery for Observable
function wrapIterator(tag, iterator, fnGetValue) {
if (fnGetValue) {
return {
get [Symbol.toStringTag]() {
return tag;
},
next() {
const next = iterator.next();
if (next.value) next.value = fnGetValue(next.value);
return next;
},
*[Symbol.iterator]() {
for (let entry of iterator) {
yield fnGetValue(entry);
}
}
}
}
else {
return {
get [Symbol.toStringTag]() {
return tag;
},
next() {
return iterator.next();
},
*[Symbol.iterator]() {
yield* iterator;
}
}
}
}
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