Created
April 16, 2020 00:07
-
-
Save danswick/6fd1f3dd54b36e642c792965c142251e to your computer and use it in GitHub Desktop.
baygeo class 4 interactive overview of class concepts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Display a map</title> | |
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" /> | |
<!-- 🔰How web pages work: linking to external resources --> | |
<script src="https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.js"></script> | |
<link href="https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.css" rel="stylesheet" /> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
} | |
#map { | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
width: 100%; | |
} | |
/* 🔰🔎 Languages of the web: CSS */ | |
#instructions { | |
position: relative; | |
top: 1em; | |
left: 2em; | |
background-color: #fff; | |
padding: 2em; | |
border-radius: 3px; | |
width: 250px; | |
} | |
#instructions .disclaimer { | |
color: #777; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="map"></div> | |
<!-- 🔰🔎 Languages of the web: HTML --> | |
<div id="instructions"> | |
<h2>COVID-19 testing centers</h2> | |
<p>Click anywhere on the map to find your nearest COVID-19 testing center. Click on an icon to read more about each center.</p> | |
<p class="disclaimer"><em>This map is for demonstration purposes only. The data provided is incomplete and likely out of date</em>.</p> | |
</div> | |
<script> | |
// Add your Mapbox access token | |
mapboxgl.accessToken = 'pk.eyJ1IjoiZGFuc3dpY2siLCJhIjoiY2swb2g4Z3hqMGFzYTNjbXdxc3F6dWZlaSJ9.Jo1SOIQUg0JV6HK4wEv36w'; | |
// Initialize the map | |
var map = new mapboxgl.Map({ | |
container: 'map', // container id | |
style: 'mapbox://styles/mapbox/dark-v10', // stylesheet location | |
center: [-122.16114705803852, 37.667635602875905], // starting position [lng, lat] | |
zoom: 9.5 // starting zoom | |
}); | |
/* 🔰Adding data to your web map: GeoJSON */ | |
// Create a variable for the testing site data. | |
// You'll reference this later for two different reasons: | |
// 1. Adding a new layer to the map to display each testing location. | |
// 2. Using the Matrix API [https://docs.mapbox.com/api/navigation/#matrix] | |
// to determine the closest testing site. | |
var testingLocations = { | |
"type": "FeatureCollection", | |
"features": [ | |
{ | |
"type": "Feature", | |
"properties": { | |
"location": "7200 Stevenson Blvd., Fremont", | |
"description": "The testing center doesn't require a medical referral. Tests are available for symptomatic members of the public. It's drive-thru only (no walk-up tests allowed) and you'll have to wait in your car. Schedule a test in advance by calling (510) 789-7231." | |
}, | |
"geometry": { | |
"type": "Point", | |
"coordinates": [ | |
-121.996434, | |
37.506389 | |
] | |
} | |
}, | |
{ | |
"type": "Feature", | |
"properties": { | |
"location": "Hayward fire station #7. 28270 Huntwood Ave", | |
"description": "Tests are open to symptomatic or otherwise high-risk members of the public at Hayward's Fire Station #7. No appointment is necessary." | |
}, | |
"geometry": { | |
"type": "Point", | |
"coordinates": [ | |
-122.064078, | |
37.632805 | |
] | |
} | |
}, | |
{ | |
"type": "Feature", | |
"properties": { | |
"location": "Oakland old Kaiser Convention Center. 10 Tenth St.", | |
"description": "The testing center at the old Kaiser Convention Center is open for those who are at high risk of contracting the virus, which includes healthcare providers, grocery/food bank/restaurant employees, homeless service workers, funeral home employees, childcare workers and caregivers. The drive-thru testing center is by appointment only and people will need to have a 'prior arrangement through certain organizations and businesses.'" | |
}, | |
"geometry": { | |
"type": "Point", | |
"coordinates": [ | |
-122.263473, | |
37.797857 | |
] | |
} | |
}, | |
{ | |
"type": "Feature", | |
"properties": { | |
"location": "Piers 30-32, San Francisco", | |
"description": "Mayor London Breed announced Monday the city would be opening a drive-thru and walk-thru testing center at Piers 30-32. For the time being, the center is only open to \"frontline workers,\" which includes sheriff's staff, city police, firefighters, paramedics, 911 dispatchers and city healthcare workers. Those who qualify will receive an e-mail and need to make an appointment." | |
}, | |
"geometry": { | |
"type": "Point", | |
"coordinates": [ | |
-122.3870, | |
37.78637 | |
] | |
} | |
} | |
] | |
} | |
// Use an event listener to wait for the map to finish loading completely | |
// before adding any new layers. If you try to add layers before the style | |
// has finished loading, those layers won't have anywhere to go. | |
// 🔰 Runtime styling: how to move past the basemap-overlay paradigm. | |
map.on('load', function () { | |
/* 🔰Client side rendering: add new layers at runtime */ | |
// Add a layer for the testing location background. | |
// There is a separate layer for the testing location | |
// icons, but those icons get a little lost without a background. | |
map.addLayer({ | |
id: 'testing-locations-background', | |
source: { | |
type: 'geojson', | |
data: testingLocations | |
}, | |
type: 'circle', | |
paint: { | |
'circle-color': '#f785a2', | |
'circle-opacity': 0.8, | |
'circle-radius': 15 | |
} | |
}); | |
// Add a layer to house the route line that will | |
// display a driving route between the clicked point | |
// and the nearest testing site. 📝 Note that this layer | |
// has a blank source.data property. This is because the | |
// route hasn't been determined yet. Later, when the | |
// user requests a route, it can be added. | |
map.addLayer({ | |
id: 'directions-route', | |
source: { | |
type: 'geojson', | |
data: { "type": "FeatureCollection", "features": [] } | |
}, | |
type: 'line', | |
paint: { | |
'line-color': '#ddd', | |
'line-opacity': 0.9, | |
'line-width': 5 | |
}, | |
layout: { | |
'line-join': 'round' | |
} | |
}); | |
// Add a new layer for the destination point. | |
map.addLayer({ | |
id: 'directions-destination', | |
source: { | |
type: 'geojson', | |
data: { "type": "FeatureCollection", "features": [] } | |
}, | |
type: 'circle', | |
paint: { | |
'circle-color': '#fff', | |
'circle-opacity': 0.5, | |
'circle-radius': 11 | |
} | |
}); | |
// Add a new layer for the testing location icons. | |
// This layer is added last so it is always visible | |
// on top of all the other dynamic layers above. | |
map.addLayer({ | |
id: 'testing-locations', | |
source: { | |
type: 'geojson', | |
data: testingLocations | |
}, | |
type: 'symbol', | |
layout: { | |
'icon-image': 'hospital-15' | |
} | |
}); | |
}); | |
/* 🔰JavaScript fundamentals: creating variables you can reference later */ | |
// Create a marker to denote the location where the user clicked. | |
// The marker does not yet have a lngLat property and hasn't been | |
// added to the map yet. Those steps will happen in response to a click event. | |
var choiceMarker = new mapboxgl.Marker({ color: "#7dc16e" }); | |
/* 🔰Listening for and responding to Events */ | |
/* 🔰 Adding custom interactivity to your web map */ | |
// Use a click listener to respond to the user's clicks. | |
map.on('click', function (e) { | |
/* 🔰Client-side rendering: querying features from the map */ | |
// Use queryRenderedFeatures to get information about the features | |
// whose locations are coincident with the click event. Use | |
// the `layers` option to limit results to a specific set of layers. | |
var clickedLocations = map.queryRenderedFeatures(e.point, { layers: ['testing-locations'] }); | |
// Sometimes, when the user clicks on the map, they'll want | |
// to get information about one of the testing locations. Other times, | |
// they'll want to find the nearest location and get directions. | |
// Use a conditional statement to guess the user's intention. If | |
// the queryRenderedFeatures call from above yields some results | |
// (remember, it's limited to the 'testing-locations' layer), then | |
// it is safe to assume that the user would like to see a popup. | |
// If not, assume they would like to find the nearest location. | |
if (clickedLocations.length > 0) { | |
// Create a new popup to contain information about the clicked feature. | |
new mapboxgl.Popup() | |
// 🔎 Traverse the clicked feature's properties to retrieve information about it. | |
.setHTML('<h2>' + clickedLocations[0].properties.location + '</h2><p>' + clickedLocations[0].properties.description + '</p>') | |
.setLngLat(e.lngLat) | |
.addTo(map); | |
} else { | |
// For all other clicks, find the nearest site and get directions to it. | |
// Also, move the Marker from above to the clicked location. | |
getMatrixData(e.lngLat); | |
choiceMarker.setLngLat(e.lngLat).addTo(map); | |
} | |
}); | |
// Change the cursor to a pointer when the it enters a feature in the 'symbols' layer. | |
map.on('mouseenter', 'testing-locations-background', function () { | |
map.getCanvas().style.cursor = 'pointer'; | |
}); | |
// Change it back to a pointer when it leaves. | |
map.on('mouseleave', 'testing-locations-background', function () { | |
map.getCanvas().style.cursor = ''; | |
}); | |
/* 🔰JavaScript fundamentals: using *functions* to do tasks for you */ | |
// The Matrix API returns a _matrix_ of travel times to all of the possible | |
// destinations. Only the closest one is important. This function accepts | |
// data from the Matrix API, finds the fastest travel time, and returns | |
// the location of the site that corresponds. | |
function determineNearestSiteCoords(inputMatrixData) { | |
// Use JavaScript's built-in `Math.min()` function to find the minimum | |
// value in the matrix data's `durations` array. | |
// 🔎 take a look at the data structure in the dev tools. | |
var minimumDuration = Math.min.apply(Math, inputMatrixData.durations[0]); | |
// The list of destinations is in the same order as the `durations` array, | |
// so use the target duration's index value to get the corresponding destination. | |
var targetDurIndex = inputMatrixData.durations[0].indexOf(minimumDuration); | |
var closestDestination = inputMatrixData.destinations[targetDurIndex]; | |
return closestDestination.location; | |
} | |
// Function to generate and send a request to the Mapbox Matrix API. | |
// This will generate a matrix of travel times between the clicked location (lngLat) | |
// and all of the possible destinations. | |
// 🔎 https://docs.mapbox.com/api/navigation/#matrix | |
function getMatrixData(lngLat) { | |
// The matrix api wants all possible locations to be in the same list. | |
// The first (or index-0) value will be the clicked location. A full matrix | |
// to and from all possible locations is not necessary. Only times from the | |
// clicked location are relevant so it will be the only source. | |
var sources = "0"; | |
// All other locations are possible destinations (note the omission of 0) | |
var destinations = "1;2;3;4"; | |
// Seed the coordinate list with the clicked location's coordinates. | |
// Later, each of the testing locations' coordinates will be appended. | |
var coordinatesList = `${lngLat.lng},${lngLat.lat}`; | |
/* 🔰 JavaScript fundamentals: using *loops* to do an | |
action for each item in a list. */ | |
// Loop through the testing locations GeoJSON and add each location's | |
// coordinates, as well as a semicolon, to the coordinates list. | |
testingLocations.features.forEach(function (feature) { | |
coordinatesList += ";" + feature.geometry.coordinates; | |
}); | |
/* 🔰 How the Internet works: interacting with web services APIs */ | |
// Using the above values, create the correct request URL. | |
var requestUrl = `https://api.mapbox.com/directions-matrix/v1/mapbox/driving/${coordinatesList}?sources=${sources}&destinations=${destinations}&access_token=${mapboxgl.accessToken}`; | |
// Send the request | |
fetch(requestUrl) | |
.then(function (response) { | |
// Parse the response | |
return response.json(); | |
}) | |
.then(function (data) { | |
/* 🔰 Working with data: data for display vs analysis */ | |
// Use the determineNearestSite function to get the closes site's coordinates from the parsed response | |
var closestDest = determineNearestSiteCoords(data); | |
// Call the displayDirections function, which will get | |
// driving directions between the clicked location and | |
// the closes site and will draw all of the relevant bits | |
// of data on the map. | |
displayDirections([lngLat.lng, lngLat.lat], closestDest); | |
}); | |
} | |
// Function to request driving directions between the location the user clicked | |
// and the nearest testing site. When a result is received, draw both the destination | |
// and the driving route on the map. | |
function displayDirections(startLngLat, endPoint) { | |
// Formulate the request URL | |
var requestUrl = `https://api.mapbox.com/directions/v5/mapbox/driving/${startLngLat};${endPoint}?geometries=geojson&access_token=${mapboxgl.accessToken}`; | |
// Send the request | |
fetch(requestUrl) | |
.then(function (response) { | |
// Parse the response | |
return response.json(); | |
}) | |
.then(function (data) { | |
/* 🔰 JavaScript fundamentals: objects */ | |
// Turn the destination's coordinates into a GeoJSON point feature | |
// so it can be added to the map. | |
var endpointGeoJSON = { "type": "Feature", "properties": {}, "geometry": { "type": "Point", "coordinates": endPoint } } | |
/* 🔰 Client-side rendering and runtime styling: | |
add and style data dynamically. */ | |
// Get the directions-destination and directions-route sources and | |
// set their data to the destination point and driving directions | |
// route, respectively. | |
map.getSource('directions-destination').setData(endpointGeoJSON); | |
map.getSource('directions-route').setData(data.routes[0].geometry); | |
}); | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment