Public
Edited
Mar 17, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
vanAreas
Insert cell
vl.vconcat(
vl.hconcat(
vlmaps('mercator'),
vlmaps("albersUSA"),
vlmaps('equalEarth')
),
vl.hconcat(
vlmaps('identity'),
vlmaps('stereographic'),
vlmaps()
)
).title('Cartographic Projections with Vega-Lite').render()
Insert cell
//Function to create a map in vega lite
function vlmaps(proj) {

//selection on hover
const selection = vl.selectPoint()
.on("mouseover")
.clear('mouseout')
.resolve("intersect");

//markGeoshape defines our map
return vl.markGeoshape({ fill: "grey", stroke: "black" })
.project(vl.projection(proj)) //projection to be used
.data(vanAreas.features) //vanAreas is geoJSON data. vanAreas.features is an array that defines the features of each neighbourhood
.params(selection)
.encode(
vl.tooltip().fieldN("properties.NAME"), //get the name of each neighbourhood
vl.opacity().if(selection, vl.value(0.8)).value(0.1) //if hovered on, make the opacity of nonselected neighbourhoods .1
)
.title(proj);
}
Insert cell
Insert cell
dparking
Insert cell
dparking1 = d3.csv("https://gist.githubusercontent.com/drlynb/2b4ac0221b8138ade06cd91886b95f69/raw/a45e5883da217c0c1b3237df307136b5ee86a7e9/disparking2.csv")
Insert cell
viewof cell = {
const selection = vl.selectPoint().on("mouseover").clear("mouseout");

//disabled parking dots
const disabledParking = vl
.markCircle({
fill: "green",
fillOpacity: 0.8,
stroke: "black",
strokeWidth: 0.5
})
.data(dparking1)
.params(selection)
.transform(vl.filter('datum.x !== "null" && datum.y !== "null"'))
.project(vl.projection("mercator"))
.encode(
vl.latitude().fieldQ("latitude"),
vl.longitude().fieldQ("longitude"),
vl.size().value(60),
vl.tooltip([
{ field: "LOCATION", title: "Location"},
{ field: "DESCRIPTN", title: "Details" },
{ field: "NOTES", title: "Notes"}
]),
vl.opacity().if(selection, vl.value(1)).value(0.3)
);

// base map of Vancouver
const map = vl
.markGeoshape({ filled: false, stroke: "#bbbbbb", strokeWidth: 1 })
.data(vanStreets.features);

return vl
.layer(map, disabledParking)
.project(vl.projection("mercator"))
.width(900)
.height(560)
.config({ view: { stroke: null } })
.render();
}
Insert cell
Insert cell
Insert cell
L = require('leaflet@1.2.0')
Insert cell
<link href='${resolve('leaflet@1.2.0/dist/leaflet.css')}' rel='stylesheet' />
Insert cell
Insert cell
Insert cell
//Our function is in map

map = {
// You'll often see Leaflet examples initializing a map like L.map('map'),
// which tells the library to look for a div with the id 'map' on the page.
// In Observable, we instead create a div from scratch in this cell called 'container', so it's
// completely self-contained.
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
// This component utilizes "yield", which pauses the execution of this code block
// Yield returns the value of container back to the notebook, which allows the div to be placed on the page.
//This is important, because Leaflet uses the div's .offsetWidth and .offsetHeight (to get current size of the div) and size the map. If I were to only return the container at the end of this method, Leaflet might get the wrong idea about the map's size.
yield container;
// Now we create a map object and add a layer to it.
let map = L.map(container).setView([49.2527, -123.1207], 10.5); // initializes the map, sets zoom & coordinates

//This adds the tile layer to the map. The map dynamically loads tiles based on current view the experience smooth and seamless. Make sure to add attribution to your map.
let baseLayer = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
.addTo(map);

}
Insert cell
Insert cell
//Here is the condensed code

mapCondensed = {
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
let map = L.map(container).setView([49.2527, -123.1207], 10.5);
let baseLayer = L.tileLayer(
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
{
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
.addTo(map);

}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
grandCentral = {
let response = await fetch('https://nominatim.openstreetmap.org/search.php?q=grand+central+station%2C+new+york&polygon_geojson=1&format=json');
let json = await response.json();
return json[0].geojson
}
Insert cell
grandCentralMap = {
// You'll often see Leaflet examples initializing a map like L.map('map'),
// which tells the library to look for a div with the id 'map' on the page.
// In Observable, we instead create a div from scratch in this cell, so it's
// completely self-contained.

//Here, I changed the width and height of the map since I got tired of accidentally scrolling in and out while navigating through the page
let container = DOM.element('div', { style: `width:${600}px;height:${width/2}px` });
// Note that I'm yielding the container pretty early here: this allows the
// div to be placed on the page. This is important, because Leaflet uses
// the div's .offsetWidth and .offsetHeight to size the map. If I were
// to only return the container at the end of this method, Leaflet might
// get the wrong idea about the map's size.
yield container;
// Now we create a map object
//NOTE: WE DO NOT SET THE VIEW BECAUSE WE WAIT TO GET THE BOUNDS OF THE LAYER WITH OUR POLYGON
let map = L.map(container);

//Basemap layer
let baseLayer = L.tileLayer(cartoLight, {
attribution:cartoAttr,
subdomains: 'abcd',
maxZoom: 20,
minZoom: 10
}).addTo(map);

//--------------------*** ADDING A LAYER ***--------------------------
//instantiates a new geoJson layer using built in geoJson handling
//We can set the attributes of the polygons (like CSS), including line weight and colors
let grandCentralLayer = L.geoJson(grandCentral,
{
weight: 5,
color: 'grey'
})
.addTo(map);

//finds bounds of polygon and automatically gets map view to fit (useful for interaction and not having to 'cook' the map zoom and coordinates as in map instantiation
grandCentralLayer.bindPopup('Grand Central Terminal'); //tooltip
map.fitBounds(grandCentralLayer.getBounds());
}
Insert cell
Insert cell
Insert cell
VanAreas = FileAttachment("VancouverAreaSize.json").json()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
VanAreasMap = {

//here, we constrain the map to a certain size
let width = 600
let height = 350
let container = DOM.element('div', { style: `width:${width}px;height:${width/2}px` });

yield container;
// Did not use setView because we are using "fit bounds" to get the polygons to determine this
//Also, I fixed the zoom level for this map since we don't get more info by zooming in for this vis
let map = L.map(container, {maxZoom: 11, minZoom: 11});
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr
}).addTo(map);

//----------- ADDING A NEW LAYER -------
//TO-DO: let's call our new layer VanAreasLayer
let VanAreasLayer = L.geoJson(VanAreas, {style})
.bindPopup(function(d){return d.feature.properties.NAME + '<br> Pop: ' + d.feature.properties.population})
.addTo(map);

//finds bounds of polygon and automatically gets map view to fit (useful for interaction and not having to 'cook' the map zoom and coordinates as in map instantiation
map.fitBounds(VanAreasLayer.getBounds());

}
Insert cell
Insert cell
vanOneWays
Insert cell
vanStreets
Insert cell
//I defined a helper function that fetches me the types of street uses listed in the JSON

//we are using some js shorthand magic here, by first mapping only the streetuses to an array
//then we use [...new Set(ourArray)] to get uniques
streetUses = [...new Set(vanOneWays.features.map(function(d){return d.properties.streetuse}))]
Insert cell
//Simple function for getting street usage color: if it's Arterial, use red, or else blue
function getStreetUsageColor(d) {
if (d == 'Arterial')
return 'red';
else if (d == 'Residential')
return 'blue';
else
return 'black'
}
Insert cell
//Street stylizing: similar to the density map, I made a function that returns red for artery streets, blue for residential streets, and black for other categories



streetStyle = function(d){
return {
weight: 2,
color: getStreetUsageColor(d.properties.streetuse),
opacity: .9
}}
Insert cell
Insert cell
Insert cell
VanStreetMap = {

let width = 600;
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
// Did not use setView because we are using "fit bounds" to get the polygons to determine this
let map = L.map(container);

//add base layer
//We can set the minZoom and maxZoom levels of the base layer, but in this case the default is fine
let baseLayer = L.tileLayer(cartoLight, {
attribution:cartoAttr,
}).addTo(map);


//TO-DO: ADD A NEW LAYER HERE THAT SHOWS THE ONE WAY STREETS AND ARTERIES using vanOneWays
//for style, I do something similar here where I pass in an inline function for style
//{style: someStyleFunction} has to be passed in with the key 'style' here

let VanStreetLayer = L.geoJson(vanStreets, {style: streetStyle})
.bindPopup(function (d){return d.feature.properties.hblock})
.addTo(map)

map.fitBounds(VanStreetLayer.getBounds())
}
Insert cell
vanOneWays
Insert cell
Insert cell
vanFoodGardens
Insert cell
VanMarkerMap = {

let width = 600;
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
// Did not use setView because we are using "fit bounds" to get the polygons to determine this
let map = L.map(container);

//add base layer
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr,
subdomains: 'abcd',
maxZoom: 20,
minZoom: 10
}).addTo(map);

//tooltip to define our content that we want to return
let tooltip = function (Layer) { return Layer.feature.properties.name + '<br> Address: ' + Layer.feature.properties.merged_address + '<br> Neighborhood: ' + Layer.feature.properties.geo_local_area; }

//As a reminder: here's an alternative way to write the same function above with arrow notation
// let tooltip = (Layer) => { Layer.feature.properties.name + '<br> Address: ' + Layer.feature.properties.merged_address + '<br> Neighborhood: ' + Layer.feature.properties.geo_local_area; }

//****** TRY UNCOMMENTING THE CODE IN THIS SECTION to define a custom green icon *******************
var myIcon = new L.Icon({
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
iconSize: [25, 41],
iconAnchor: [12, 41],
//popupAnchor: [1, -34],
//shadowSize: [41, 41]
});

let customMarker = L.marker([49.28, -123.12], {icon: myIcon}).addTo(map)
.bindPopup('A pretty CSS3 popup added to the map.<br> Easily customizable.')
.openPopup();

//*****************************************************************************************
//Adds the layer to the map & bind tooltip
let VanAreasLayer = L.geoJson(vanFoodGardens)
.bindPopup(tooltip)
.addTo(map);

// //finds bounds of polygon and automatically gets map view to fit (useful for interaction and not having to 'cook' the map zoom and coordinates as in map instantiation
map.fitBounds(VanAreasLayer.getBounds());
}
Insert cell
Insert cell
VanCircleFoodMap = {

let width = 600;
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
// Did not use setView because we are using "fit bounds" to get the polygons to determine this
let map = L.map(container);

//add base layer
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr,
}).addTo(map);

//geoMarker obj
let geojsonMarkerOptions = {
radius: 5,
fillColor: "#ff7800", //orange
color: "#ff7800",
weight: 1,
opacity: 1,
fillOpacity: 0.6
};


//passing a pointToLayer function in a GeoJSON options object when creating the GeoJSON layer
//This function is passed a LatLng and should return an instance of ILayer, in this case likely a Marker or CircleMarker.
let VanAreasLayer = L.geoJSON(vanFoodGardens, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
}})

//bind popup and add layer to map
.bindPopup(function (Layer) {
return Layer.feature.properties.name + '<br> Address: ' +
Layer.feature.properties.merged_address + '<br> Neighborhood: ' +
Layer.feature.properties.geo_local_area;
})
.addTo(map);


//finds bounds of polygon and automatically gets map view to fit
map.fitBounds(VanAreasLayer.getBounds());
}

Insert cell
Insert cell
Insert cell
dparking
Insert cell
VanDisabledParking = {

let width = 600;
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
// Did not use setView because we are using "fit bounds" to get the polygons to determine this
let map = L.map(container);

//add base layer
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr,
}).addTo(map);

//geoMarker obj
let geojsonMarkerOptions = {
radius: 5,
fillColor: "green", //orange
color: "white",
weight: .5,
opacity: .9,
fillOpacity: 1
};


//passing a pointToLayer function in a GeoJSON options object when creating the GeoJSON layer
//This function is passed a LatLng and should return an instance of ILayer, in this case likely a Marker or CircleMarker.
let VanAreasLayer = L.geoJSON(dparking, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
}})

//bind popup and add layer to map
.bindPopup(function (Layer) {
return 'LOCATION: ' +
Layer.feature.properties.LOCATION + '<br> DESCRIPTION: ' +
Layer.feature.properties.DESCRIPTN + '<br> NOTES: ' +
Layer.feature.properties.NOTES;
})
.addTo(map);


//finds bounds of polygon and automatically gets map view to fit
map.fitBounds(VanAreasLayer.getBounds());
}

Insert cell
Insert cell
Insert cell
vanCrime
Insert cell
crimePoints
Insert cell
FilteredGeoCrimes
Insert cell
Insert cell
Insert cell
Insert cell
vanCrimeMap = {

let width = 600;
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;

//Use setview when making heatmaps, because heatLayer doesn't have a getBounds() method
let map = L.map(container).setView([49.2527, -123.1207], 10.5);

//add base layer
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr,
}).addTo(map);

let heatStyle = {
radius: 10,
blur: 15,
maxZoom: 10,
max: 4.0
}

//our heatmap layer
let crimeLayer = heatLayer(crimePoints, heatStyle).addTo(map);

//Of course, we can always add more layers on top, such the neighborhood boundaries
//TO-DO: LET'S TRY THAT NOW.
let VanAreasLayer = L.geoJson(VanAreas, {style: neighbourhoodStyle})
.bindPopup(d => d.feature.properties.NAME + '<br> Pop: ' + d.feature.properties.population)
.addTo(map);


}

Insert cell
Insert cell
VanTwoLayers = {

let width = 600;
let container = DOM.element('div', { style: `width:${width}px;height:${width/1.6}px` });
yield container;
// Did not use setView because we are using "fit bounds" to get the polygons to determine this
let map = L.map(container);

//add base layer
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr,
subdomains: 'abcd',
maxZoom: 20,
minZoom: 0
}).addTo(map);

//geoMarker obj
let geojsonMarkerOptions = {
radius: 5,
fillColor: "#ff7800",
color: "#ff7800",
weight: 1,
opacity: 1,
fillOpacity: 0.6
};

//THIS IS THE NEIGHBOURHOODS LAYER.
let VanAreasLayer = L.geoJson(VanAreas, {style: neighbourhoodStyle})
.bindPopup(d => d.feature.properties.NAME + '<br> Pop: ' + d.feature.properties.population)
.addTo(map);
//THIS IS THE POINTS LAYER, TRY MOVING IT SO THAT IT IS BOUND BEFORE VanPointsLayer
let VanPointsLayer = L.geoJSON(vanFoodGardens, {
pointToLayer: (feature, latlng) => {
return L.circleMarker(latlng, geojsonMarkerOptions);
}})
.bindPopup(function (Layer) {
return Layer.feature.properties.name + '<br> Address: ' +
Layer.feature.properties.merged_address + '<br> Neighborhood: ' +
Layer.feature.properties.geo_local_area;
})
.addTo(map);



//finds bounds of polygon and automatically gets map view to fit
map.fitBounds(VanAreasLayer.getBounds());
}

Insert cell
Insert cell
Insert cell
This is the CSS defining the legend
<link href='${resolve(
'leaflet@1.2.0/dist/leaflet.css'
)}' rel='stylesheet' /><style>

.legend {
line-height: 18px;
color: #555;
border: 1px solid #000000;
border-radius: 4px;
padding: 8px;
background: rgba(255,255,255,0.8);
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}</style>
Insert cell
popMap2 = {
let width = 600
let height = 600
let container = DOM.element('div', { style: `width:${width}px;height:${width/2}px` });
yield container

//base layer
let map = L.map(container);
let baseLayer = L.tileLayer(cartoLight, {
attribution: cartoAttr,
minZoom: 11,
maxZoom: 11,
}).addTo(map);

//add van areas
let VanAreasLayer = L.geoJson(vanAreas, {style: style})
.bindTooltip(function (Layer) {
return Layer.feature.properties.NAME;
}).addTo(map);


//DEFINING OUR LEGEND
var legend = L.control({position: 'bottomright'});

legend.onAdd = function (map) {
var div = L.DomUtil.create('div', 'info legend'),
grades = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
labels = [];

// loop through our density intervals and generate a label with a colored square for each interval
for (var i = 0; i < grades.length; i++) {
div.innerHTML +=
'<i style ="background-color:' + getDensColor(grades[i]) + '"></i> ' +
grades[i] + (grades[i + 1] ? '&ndash;' + grades[i + 1] + '<br>' : '+');
}
return div;
};

legend.addTo(map);
map.fitBounds(VanAreasLayer.getBounds());
}
Insert cell
Insert cell
//Try creating a map here

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
//We are writing the event listener function for each feature in our geojson layer
//when each of these events are fired, a callback function is triggered
//more interaction (such as hover) is covered here: https://leafletjs.com/examples/choropleth/

function featureEvents(feature, layer) {
layer.on({
click: whenClicked, //callback functions
});
}
Insert cell
//We update our data in the function using mutable. This overrides observables defaults so we can push data from within //function scope globally
whenClicked = (event) => {
mutable nHood = event.target.feature.properties.NAME
}
Insert cell
//Mutable overrides Observable's defaults state, so we can push data from within function scope globally
mutable nHood = null
Insert cell
mapSelection = {
if(nHood == null ){
return Array.from(new Set(vanCrime2.map(d => d.NEIGHBOURHOOD))) //Set is a new datastructure in ES6 -> only unique values
} else {
return [nHood]
}
}
Insert cell
selectedNeighbourhoods = selectedNames(mapSelection);
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
heatLayer = L, require('leaflet.heat').catch(() => L.heatLayer)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function style(feature) {
return {
fillColor: getDensColor(feature.properties.density),
weight: 2,
opacity: .5,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
}
Insert cell
function popStyle(feature) {
return {
fillColor: getPopColor(feature.properties.population),
weight: 2,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
}
Insert cell
function neighbourhoodStyle(){
return {
fillColor: '#999999',
weight: 2,
opacity: .5,
color: 'white',
dashArray: '3',
fillOpacity: 0.2,
}};
Insert cell
Insert cell
Insert cell
vanFoodGardens = FileAttachment("community-gardens-and-food-trees.geojson").json()
Insert cell
vanAreas = FileAttachment("VanAreas.json").json()
Insert cell
vanAreaValues2 = d3.json("https://www.sfu.ca/~lyn/data/Urban/VancouverAreaSizeTopo.json")
Insert cell
vanStreets = FileAttachment("public-streets.geojson").json()
Insert cell
vanOneWays = FileAttachment("one-way-streets.geojson").json()
Insert cell
dparking = d3.json("https://gist.githubusercontent.com/drlynb/6dffc7ccdaf774ee031dc46c909721b2/raw/8a8b8fb6b6b60e66980cbb9f897b198bac23dbf7/testparking.geojson")
Insert cell
Insert cell
vanCrime2 = {
let i=vanCrime.features.length;
let j=0;
let k=0;
let temp ={};
let newCrime=[];
for(k=0;k<i; k++)
{
newCrime[k]= vanCrime.features[k].properties;
}
return (newCrime);
}
Insert cell
Insert cell
Insert cell
crimePoints = FilteredGeoCrimes.features.map(feature =>
feature.geometry.coordinates.slice().reverse().concat([0.1]))
Insert cell
Insert cell
Insert cell
Insert cell
vanCrimeFiltered = vanCrime.features.filter(d => bSelection.includes(d.properties.Offense))
.filter(d => timeParse(d.properties.Date) >= timeParse(TSelection[0]) &&
timeParse(d.properties.Date) <= timeParse(TSelection[1]))
.filter(d => mapSelection.includes(d.properties.NEIGHBOURHOOD))
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