Published
Edited
Jul 4, 2022
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
Insert cell
Insert cell
function* stJoeMap() {
// Method for yielding DOM in Observable
let container = DOM.element('div', { style: `width:940px;height:600px` });
yield container;
// Choose baseMap and create Leaflet map object
const baseMap = L.tileLayer(m.POS[0],m.POS[1])
let map = L.map(container).setView([42, -85.5], 9);
baseMap.addTo(map);
// Create LatLon on mouseClick
const popup = L.popup();
const onMapClick = (e) => {popup.setLatLng(e.latlng)
.setContent(`You clicked the map at: <h3>${e.latlng.toString()} </h3>`).openOn(map)};
map.on('click', onMapClick);
// Styling for geojson layers, colors SF buildings based on height
const simplePolygon = {"color":"#00ebc4", "weight":.75, "opacity":.7, "fillOpacity":0};
const gageStyle = (feature) => {
const d = feature.properties.status;
const gColor = d == 'LOW' ? 'red' : d == 'MUCH BELOW NORMAL' ? 'orangered' : d == 'BELOW NORMAL' ? 'orange' : d == 'NORMAL' ? 'green' : d == 'ABOVE NORMAL' ? 'darkblue' : d == 'MUCH ABOVE NORMAL' ? 'black': 'grey';
return { radius: 15, fillColor: gColor, color: "grey", weight: 3, fillOpacity: 0.8};
};

// Highlight features on mouseOver
const highlightFeature = (e) => {
const layer = e.target;
layer.setStyle({ weight: 4, color: '#32cfb1', dashArray: '', fillOpacity: 0.6 });
layer.bringToFront();
info.update(layer.feature.properties);
}
// Un-highlight with mouseOut
const resetHighlight = (e) => {
layer1.resetStyle(e.target);
info.update();
}
// zoom to feature onClick
const zoomToFeature = (e) => map.fitBounds(e.target.getBounds());
// Bind popups to each feature, bPop - buildings, pPop - Parcels
function bPop (feature,layer) {
const p = feature.properties;
console.log(p);
layer.bindPopup(`<h3>${p.info}</h3>
<ul><li>Gage Status: <b>${p.status} </b></li>
<li>Discharge: <b>${p.stationInfo.siteFlow} ft3/sec</b></li>
<li>Gage Height: <b>${p.stationInfo.gageHeight} ft</b></li>
<li>Station ID: <b>${p.ID}</b></li>`)
.on({ mouseover: highlightFeature,mouseout: resetHighlight,click: zoomToFeature })};
// Generates the info div box in the upper right hand corner
let info = L.control();
info.onAdd = function (map) {
this._div = L.DomUtil.create('div', 'info'); // create a div with a class "info"
this.update();
return this._div;
};
// method that we will use to update the control based on feature properties passed
info.update = function (props) {
this._div.innerHTML = '<h4>STATION STATUS:</h4>' + (props ?
'<b>'+props.status +'</b><br />'+ props.info + '<br />' + props.stationInfo.siteFlow + ' ft3/s' + ' - ' + props.stationInfo.gageHeight + ' ft ' + ' - ' + ' ID: ' + props.ID: 'Hover over a station')};
info.addTo(map);
// Add SF Buildings GeoJSON layer and add to map
//let layer1 = L.geoJSON(geoData1, { style: layer2Style, onEachFeature: bPop }).addTo(map);
const layer1 = L.geoJSON(geoData1, {style: gageStyle, onEachFeature: bPop,pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng);
}
}).addTo(map);
}
Insert cell
Insert cell
stationData = {
const currentFlow = async(sta_list) => {
const urlStart = 'https://waterservices.usgs.gov/nwis/iv/?format=json&sites=', urlEnd = '&parameterCd=00060,00065&siteStatus=all';
let gageData = {}, gageList = [], z;
for(z of sta_list) {
const rawData = await fetch(`${urlStart}${z}${urlEnd}`);
const data = await rawData.json();
const {sourceInfo,values,variable} = data.value.timeSeries[0];
const {sourceInfo: sourceInfo2, values: values2, variable: variable2} = data.value.timeSeries[1];
gageData = {siteName: sourceInfo.siteName, siteCode: sourceInfo.siteCode[0].value, siteFlow: Number(values[0].value[0].value), flowUnits: variable.unit.unitCode, gageHeight: Number(values2[0].value[0].value), heightUnits: variable2.unit.unitCode, dateTime: values[0].value[0].dateTime, huc: sourceInfo.siteProperty[1].value, lat: Number(sourceInfo.geoLocation.geogLocation.latitude), lon: Number(sourceInfo.geoLocation.geogLocation.longitude), crs: sourceInfo.geoLocation.geogLocation.srs };
gageList.push(gageData);
}
return gageList;
};
const stations = req2.params.stationlist.split(',');
const data = await currentFlow(stations);
return data;
}
Insert cell
gageStatus = {
const gageStatus = async (stationList) => {
let x,z;
let stationData = [];
for (z in stationList) {
const urlStart = 'https://waterservices.usgs.gov/nwis/stat/?format=rdb&sites=', urlEnd = '&statReportType=daily&statTypeCd=all&parameterCd=00060';
const rawData = await fetch(`${urlStart}${stationList[z].siteCode}${urlEnd}`);
const dataLines = (await rawData.text()).split("\n");
let dailyMean = 0, dailyStats = {"mean":0};
//Need to find todays date to compare current discharge level against
const today = new Date(), monthNum = (today.getMonth() + 1), dayNum = today.getDate(), yearNum = today.getFullYear();
const usgsLabels = {"p05":'LOW', "p10":'MUCH BELOW NORMAL', "p20":'BELOW NORMAL', "p25":'BELOW NORMAL',
"p50":'NORMAL', "p75": 'NORMAL', "p80":'ABOVE NORMAL', "p90":'ABOVE NORMAL', "p95":'MUCH ABOVE NORMAL'};
//start at the January 1st to match date to today's date, line 50 in the tab separated text file
for (x=49;dailyStats.mean==0;x++) {
const dataTabs = dataLines[x].split("\t");
dailyStats.mean = (dataTabs[5] == monthNum && dataTabs[6] == dayNum) ? (dataTabs[14]) : 0;
dailyStats.mean != 0 ? (dailyStats = {"mon":dataTabs[5],"day":dataTabs[6], "maxYear": dataTabs[10], "maxFlow": dataTabs[11], "minYear": dataTabs[12], "minFlow": dataTabs[13], "mean": dataTabs[14], "p05": dataTabs[15], "p10": dataTabs[16], "p20": dataTabs[17], "p25": dataTabs[18], "p50":dataTabs[19], "p75":dataTabs[20], "p80":dataTabs[21], "p90":dataTabs[22], "p95":dataTabs[23]}):""};
//find percentile current flow
for (x=7;x<Object.entries(dailyStats).length;x++) {
stationList[z].siteFlow >= Object.entries(dailyStats)[x][1]? (stationList[z].gagePercent = Object.entries(dailyStats)[x][0],
stationList[z].gageStatus = usgsLabels[stationList[z].gagePercent]):"";
}
const stationRecord = {"ID": stationList[z].siteCode, "stationInfo": stationList[z], "dailyStats": dailyStats};
stationData.push(stationRecord);
};
return stationData;
};
return gageStatus(stationData);
}
Insert cell
geoData1 = {
const geoData = async (data) => {
const geo = new FeatureCollection('USGS Gaging Stations and Status for the St. Joeseph River, MI - Code by https://github.com/Nottawaseppi');
data.forEach(d => {
console.log(d);
const gp = new GeoPoint(d.ID,[d.stationInfo.lon,d.stationInfo.lat],d);
[gp.properties.source,gp.properties.info,gp.properties.status] = ['USGS/NHBP',d.stationInfo.siteName, d.stationInfo.gageStatus]
console.log(gp);
geo.fPush(gp);
});
return geo;
};
const geoStations = await geoData(gageStatus);
return geoStations;
}
Insert cell
Insert cell
fetchData = async(stations) =>{
const rawData = await fetch(`https://usgs-gage.firebaseapp.com/usgs-data-live/gageGeo/${stations}`);
return await rawData.json();
}
Insert cell
function getGage (gage) {
const props = gage.properties;
const statCol = {"p05":'red', "p10":'orangered', "p20":'orange', "p25":'orange', "p50":'green', "p75": 'green', "p80":'blue', "p90":'darkblue', "p95":'#00034d'};
const statBor = {"p05":'13px solid red', "p10":'13px solid redorange', "p20":'13px solid orange', "p25":'13px solid orange', "p50":'13px solid green', "p75": '13px solid green', "p80":'13px solid blue', "p90":'13px solid darkblue', "p95":'13px solid #00034d'};
const divStyle = [{'textAlign':'center'}, {'fontFamily':'Arial, Helvetica, sans-serif'},{'background-color':'#2e2e2e'},{'margin':'0px'},{'padding':'15px'}];
const txtStyles = [[{'color':'#0069cc'},{'margin':'4px'},{'lineHeight':'35px'}],
[{'color':'#045c72'},{},{'margin':'0px'}, {'lineHeight':'15px'}],
[{'color':statCol[props.stationInfo.gagePercent]},{},{'margin':'7px'}, {'lineHeight':'32px'}], [{'color':'#8fbeff'},{'fontSize':'20px'},{'margin':'0px'},{'lineHeight':'25px'}],
[{'color':'#8fbeff'},{'fontSize':'18px'},{'margin':'2px'},{'lineHeight':'25px'}]];
const txtSizes = ['H1','P','H1','P','P'];
const txt = [props.info, (`Current Status for USGS Gage: ${props.ID}`),props.stationInfo.gageStatus,
(`FLOW: ${props.stationInfo.siteFlow} cfs - HEIGHT: ${props.stationInfo.gageHeight} ft`),
(`Daily Mean: ${props.dailyStats.mean} cfs - Min: ${props.dailyStats.minFlow} cfs - Max: ${props.dailyStats.maxFlow} cfs`) ];
const widget1 = new Widget(props.ID, divStyle);
widget1.addBorder(statBor[props.stationInfo.gagePercent]);
txtStyles.forEach( (d,i) => widget1.addText(txtSizes[i],txt[i],d) );
return widget1.div;
}
Insert cell
function* drawWidgets (data) {
let cont = DOM.element('div', { style: `width:600px;` });
yield cont;
data.features.forEach(d => cont.appendChild(getGage(d)));
}
Insert cell
Insert cell
gageData = fetchData('04096405,04097500,04099000,04101000,04101500')
Insert cell
drawWidgets(gageData)
Insert cell
Insert cell
Insert cell
function* stationList (data) {
let cont = DOM.element('div', { style: `width:960px;` });
yield cont;
const divStyle = [{'textAlign':'left'}, {'fontFamily':'Arial, Helvetica, sans-serif'},{'margin': '10px'},
{'padding':'10px'},{'color':'darkblue'},{'borderRadius':'25px'},{'list-style-type': 'square'},
{'box-shadow': '5px 8px #e3e3e3'}];
const statCol = {"p05":'red', "p10":'orangered', "p20":'orange', "p25":'orange', "p50":'green', "p75": 'green', "p80":'blue', "p90":'darkblue', "p95":'#00034d'};
const widg = new Widget('list',divStyle);
widg.addBorder('5px solid lightblue');
widg.addText('H2','St. Joseph River Gages Summary',[{'color':'darkblue'}])
data.features.forEach(d => {
const props = d.properties;
const txt = [(`${props.info} - ${props.ID} - ${props.stationInfo.gageStatus} -
${props.stationInfo.siteFlow} cfs`)];
const styles = [{'color':statCol[props.stationInfo.gagePercent]},{'font-weight': 'bold'}];
widg.addList('LI', txt, styles);
})
cont.appendChild(widg.div);
}
Insert cell
stationList(geoData1);
Insert cell
Insert cell
async function* basinStatus (data) {
let cont = DOM.element('div', { style: `width:530px;height:276px` });
yield cont;
const divStyle = [{'textAlign':'center'}, {'fontFamily':'Arial, Helvetica, sans-serif'},
{'background-color':''},{'margin':'0px'},{'padding':'30px'},{'borderRadius':'25px'}];
let statAcc=[{'s':'p05','num':0},{'s':'p10','num':0},{'s':'p20','num':0},{'s':'p25','num':0},
{'s':'p50','num':0},{'s':'p75','num':0},{'s':'p80','num':0},{'s':'p90','num':0},{'s':'p95','num':0}];
const usgsLabels = {"p05":'LOW', "p10":'MUCH BELOW NORMAL', "p20":'BELOW NORMAL', "p25":'BELOW NORMAL', "p50":'NORMAL', "p75": 'NORMAL', "p80":'ABOVE NORMAL', "p90":'ABOVE NORMAL', "p95":'MUCH ABOVE NORMAL'};
const statCol = {"p05":'red', "p10":'redorange', "p20":'orange', "p25":'orange', "p50":'green', "p75": 'green', "p80":'blue', "p90":'darkblue', "p95":'#00034d'};
const statBor = {"p05":'15px solid red', "p10":'15px solid redorange', "p20":'15px solid orange', "p25":'15px solid orange', "p50":'15px solid green', "p75": '15px solid green', "p80":'15px solid blue', "p90":'15px solid darkblue', "p95":'15px solid #00034d'};
data.features.forEach(d => {
statAcc.forEach(e => d.properties.stationInfo.gagePercent == e.s ? e.num++ : '');
});
const getMedian = statAcc.reduce((a,d) => {
a.num == 0 ? a = d : (a.num < d.num ? a = d : '');
return a;
});
const widg = new Widget('indicator',divStyle);
const medianTxt = usgsLabels[getMedian.s];
const styles = [{'color':statCol[getMedian.s]},{'font-weight': 'bold'},{'font-size':'50px'}];
widg.addText('H3','St. Joseph Basin Median Gage Status');
widg.addBorder(statBor[getMedian.s]);
widg.addText('H1', medianTxt, styles);
widg.addText('H3',(`Gages Reporting: ${(data.features.length)}`));
cont.appendChild(widg.div);
console.log(getMedian);
}
Insert cell
basinStatus(geoData1);
Insert cell
Insert cell
stationList2(geoData1)
Insert cell
function* stationList2 (data) {
let cont = DOM.element('div', { style: `width:960px;` });
yield cont;
const divStyle = [{'textAlign':'left'}, {'fontFamily':'Arial, Helvetica, sans-serif'},{'margin': '10px'},
{'padding':'10px'},{'color':'darkblue'},{'borderRadius':'25px'},{'list-style-type': 'square'},
{'box-shadow': '5px 8px #e3e3e3'}];
const statCol = {"p05":'red', "p10":'orangered', "p20":'orange', "p25":'orange', "p50":'green', "p75": 'green', "p80":'blue', "p90":'darkblue', "p95":'#00034d'};
const widg = new Widget('list',divStyle);
widg.addBorder('5px solid lightblue');
widg.addText('H2','St. Joseph River Gages Summary',[{'color':'darkblue'}])
data.features.forEach(d => {
const props = d.properties;
const txt = [(`${props.info} - ${props.ID} - ${props.stationInfo.gageStatus} -
${props.stationInfo.siteFlow} cfs`)];
const styles = [{'color':statCol[props.stationInfo.gagePercent]},{'font-weight': 'bold'}];
widg.addList('LI', txt, styles);
})
cont.appendChild(widg.div);
}
Insert cell
class CardList {
constructor(id, styles) {
this.div = document.createElement('DIV');
this.type = CardList;
this.id = id;
styles != null ? styles.forEach(d => this.div.style[Object.keys(d)]=Object.values(d)) : '';
}
type() {return this.type};
id() {return this.id};
addDiv(node, id, clsName) {
node.className = clsName;
node.id = id;
this.div.appendChild(node);
}
addChildDiv(node, targetID, id, clsName) {
//node.className = clsName;
console.log(`node.id: ${node.id}`);
console.log(`node: ${node}`);
console.log(`targetID: ${targetID}`)
node.id = id;
const targetDIV = document.getElementById(targetID);
console.log(targetDIV);
//document.getElementById(targetID).appendChild(node);
targetDIV.append(node);
}
}

Insert cell
Insert cell
class Widget {
constructor(id, styles, clsName) {
this.div = document.createElement("DIV");
this.type = 'Widget';
this.div.id = id;
this.div.className = clsName;
styles != null ? styles.forEach(d => this.div.style[Object.keys(d)]=Object.values(d)) : '';
}
type() {return this.type};
//id() {return this.div.id};
addText(size, txt, styles) {
this.text = document.createElement(size);
this.text.innerText = txt;
styles != null ? styles.forEach(d => this.text.style[Object.keys(d)]=Object.values(d)) : '';
this.div.appendChild(this.text);
}
addBorder(styles) {
this.div.style.border = styles;
}
addList(type, txt, styles) {
//styles != null ? this.div.style = styles : '';
txt.forEach(d => {
const node = document.createElement(type);
node.appendChild(document.createTextNode(d));
styles != null ? styles.forEach(d => node.style[Object.keys(d)]=Object.values(d)) : '';
this.div.appendChild(node);
});
}
};
Insert cell
Insert cell
class GeoPoint extends Feature {
constructor(id,[lon,lat],props = {}) {
super(id);
this.properties.EPSG = 4326, this.properties.source = '', this.properties.crs = 'WGS 84';
this.geometry = {'type':'Point','coordinates':[lon,lat]};
Object.entries(props).forEach(([d,i]) => (this.properties[d] = i));
}
geo() {return this.geometry.coordinates}
crs() {return [(`EPSG:${this.properties.EPSG}`),this.properties.crs]}
};
Insert cell
class FeatureCollection {
constructor(meta) {
this.type = 'FeatureCollection';
this.metadata = meta;
this.features = [];
}
geoType() { try {return this.features[0].geometry.type}
catch {return "It doesn't appear their are any features yet..."} }
fCount() {return this.features.length};
fProps() {try {return this.features[0].properties}
catch {return "It doesn't appear their are any features yet..."} }
fPush(feature) {
try { this.features.push(feature);
return `Added Feature Number ${this.fCount()}`; }
catch { return "Cannot add ${feature} to Feature Collection"} }
};
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more