Published
Edited
Oct 25, 2021
1 fork
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 Live USGS St. Joseph River Gages GeoJSON layer and add to 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', "undefined":'grey'};
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
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 Widget {
constructor(id, styles) {
this.div = document.createElement("DIV");
this.type = 'Widget';
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};
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
class Feature {
constructor(id) {
this.type = 'Feature';
this.properties = {'ID':id,'label':'','info':'','status':'','val':-1,'units':''};
}
id() {return this.properties.ID}
info() {return this.properties}
addProp(name,value) {
try {
this.properties[name] = value;
return (`${name}: ${value} added to Feature ID: ${this.id()}`); }
catch { (`Could NOT add ${name}: ${value}!`) }
}
addProps(arr) {
try { arr.forEach(d => this.properties[d[0]] = d[1])}
catch { return (`Could NOT add ${arr} to ${this.id()}`) }
}
};

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

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