Published
Edited
Feb 11, 2021
Insert cell
md`# GeoJSON + Leaflet`
Insert cell
{
const context = DOM.context2d(128, 128);
for (let radius = 8; radius < 128; radius += 8) {
context.beginPath();
context.arc(0, 0, radius, 0, 2 * Math.PI);
context.stroke();
}
return context.canvas;
}
Insert cell
d3 = require("d3")
Insert cell
html`<svg width="128" height="128" fill="none" stroke="blue">${
d3.range(8, 128, 8).map(radius => `<circle r="${radius}"></circle>`)
}</svg>`


Insert cell
{
// styling with CSS
const container = html`<svg width="128" height="128" style="fill: none; stroke: red;"></svg>`;
const data = [8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120];
const circles = data.map(radius => svg`<circle r="${radius}"></circle>`)
circles.forEach(circle => container.appendChild(circle));
return container
}
Insert cell
html`<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="5"/>
</svg>`
Insert cell
container = html`<div style="height: 400px;"></div>`
Insert cell
{
var map = L.map(container).setView([39.74739, -105], 13);
L.tileLayer('https://stamen-tiles.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg', {
maxZoom: 18,
attribution: 'Map tiles by <a target="_top" rel="noopener" href="http://stamen.com">Stamen Design</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>, under <a target="_top" rel="noopener" href="http://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>',
tileSize: 512,
zoomOffset: -1
}).addTo(map);

var baseballIcon = L.icon({
iconUrl: 'baseball-marker.png',
iconSize: [32, 37],
iconAnchor: [16, 37],
popupAnchor: [0, -28]
});

function onEachFeature(feature, layer) {
var popupContent = "<p>I started out as a GeoJSON " +
feature.geometry.type + ", but now I'm a Leaflet vector!</p>";

if (feature.properties && feature.properties.popupContent) {
popupContent += feature.properties.popupContent;
}

layer.bindPopup(popupContent);
}

L.geoJSON([bicycleRental, campus], {

style: function (feature) {
return feature.properties && feature.properties.style;
},

onEachFeature: onEachFeature,

pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
radius: 8,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
});
}
}).addTo(map);

L.geoJSON(freeBus, {

filter: function (feature, layer) {
if (feature.properties) {
// If the property "underConstruction" exists and is true, return false (don't render features under construction)
return feature.properties.underConstruction !== undefined ? !feature.properties.underConstruction : true;
}
return false;
},

onEachFeature: onEachFeature
}).addTo(map);

var coorsLayer = L.geoJSON(coorsField, {

pointToLayer: function (feature, latlng) {
return L.marker(latlng, {icon: baseballIcon});
},

onEachFeature: onEachFeature
}).addTo(map);
invalidation.then(() => map.remove()); // clean up when rerunning the cell
}
Insert cell
map = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const defs = svg.append("defs");

defs.append("path")
.attr("id", "outline")
.attr("d", path(outline));

defs.append("clipPath")
.attr("id", "clip")
.append("use")
.attr("xlink:href", new URL("#outline", location));

const g = svg.append("g")
.attr("clip-path", `url(${new URL("#clip", location)})`);

g.append("use")
.attr("xlink:href", new URL("#outline", location))
.attr("fill", "#fff");

g.append("path")
.attr("d", path(graticule))
.attr("stroke", "#ddd")
.attr("fill", "none");

g.append("path")
.attr("d", path(land))
.attr("fill", "#ddd");

svg.append("use")
.attr("xlink:href", new URL("#outline", location))
.attr("stroke", "#000")
.attr("fill", "none");

svg.append("g")
.selectAll("circle")
.data(data)
.join("circle")
.attr("transform", d => `translate(${projection([d.longitude, d.latitude])})`)
.attr("r", 1.5)
.append("title")
.text(d => d.name);

return svg.node();
}
Insert cell
projection = d3.geoNaturalEarth1()
Insert cell
data = {
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson"
const response = await d3.json(url); // Python: response = json.loads(requests.get(url).text)
const features = response.features; // Python list & dictionary: features = response['features']
const data = features.map(d => ({ // Python: list comprehension & lambda
name: d.properties.title,
longitude: d.geometry.coordinates[0],
latitude: d.geometry.coordinates[1]
}));
// Observable cells are "reactive" and execute in "dependency" order.
return data; // Each cells in an Observable notebooks has a return value.
}
Insert cell
height = {
const [[x0, y0], [x1, y1]] = d3.geoPath(projection.fitWidth(width, outline)).bounds(outline);
const dy = Math.ceil(y1 - y0), l = Math.min(Math.ceil(x1 - x0), dy);
projection.scale(projection.scale() * (l - 1) / l).precision(0.2);
return dy;
}
Insert cell
path = d3.geoPath(projection)
Insert cell
outline = ({type: "Sphere"})
Insert cell
graticule = d3.geoGraticule10()
Insert cell
topojson = require("topojson-client@3")
Insert cell
world = FileAttachment("land-50m.json").json()
Insert cell
land = topojson.feature(world, world.objects.land)
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