Update hotspots for legend

This commit is contained in:
2023-10-28 15:27:48 -04:00
parent 2d411e8634
commit 7d146a772b
5 changed files with 527 additions and 419 deletions

View File

@@ -8,8 +8,8 @@ body {
height: 100vh; height: 100vh;
} }
#map { html, body, #map {
height: 100vh; height: 100%;
width: 100vw; width: 100vw;
} }
@@ -23,3 +23,17 @@ body {
.leaflet-tile-pane { .leaflet-tile-pane {
filter: brightness(50%); filter: brightness(50%);
} }
.legend {
border: solid #999999 3px;
color: #eee;
background-color: rgba(44, 44, 44, .8);
padding: 1em;
}
#legendOccGrad {
background-image: linear-gradient(to right, transparent, red);
padding: 0 2em;
text-align: center;
text-shadow: black .2em .2em;
}

View File

@@ -1,430 +1,496 @@
let map = L.map("map", { let map = L.map("map", {
zoomControl: false, zoomControl: false,
attributionControl: false, attributionControl: false,
}).setView([43.084679, -77.674702], 17); }).setView([43.084679, -77.674702], 17);
// L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { // L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
// maxZoom: 19, // maxZoom: 19,
// attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' // attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
// }).addTo(map); // }).addTo(map);
var CartoDB_DarkMatterNoLabels = L.tileLayer( var CartoDB_DarkMatterNoLabels = L.tileLayer(
"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png", "https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
{ {
attribution: attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>', '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: "abcd", subdomains: "abcd",
maxZoom: 20, maxZoom: 20,
}
).addTo(map);
// var CartoDB_PositronNoLabels = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', {
// attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
// subdomains: 'abcd',
// maxZoom: 20
// }).addTo(map); // good hacky filter: invert(100%) hue-rotate(180deg) brightness(100%) contrast(100%);
// var CartoDB_Positron = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
// attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
// subdomains: 'abcd',
// maxZoom: 20
// }).addTo(map);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function shuffle(array) {
let currentIndex = array.length,
randomIndex;
// While there remain elements to shuffle.
while (currentIndex > 0) {
// Pick a remaining element.
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
function setMarker(attrs, ref) {
// let red = parseInt("ff", 16);
// let green = parseInt("78", 16);
// let style = {"fillColor": `#${red.toString(16)}${green.toString(16)}00`};
let ratio = attrs.properties.count / attrs.properties.capacity;
let adjustedratio = ratio > 1 ? 1 : ratio;
let red = 255 * adjustedratio;
let style = { fillColor: `rgba(255, 0, 0, ${adjustedratio})` };
ref.setStyle(style);
ref.bindPopup(
`${attrs.properties.name}<br />Current Occupation: ${attrs.properties.count}<br />${Math.round(ratio*100)}% capacity`
);
}
function onEachFeature(feature, layer) {
// does this feature have a property named popupContent?
if (!feature.properties || !feature.properties.name) {
return;
}
setMarker(feature, layer);
}
const polyStyle = {
color: "#ff7800",
weight: 5,
opacity: 0.65,
};
const geojsonMarkerOptions = {
pane: "nodePane",
radius: 8,
fillColor: "#ff7800",
color: "#ff7800",
weight: 3,
opacity: 1,
fillOpacity: 1,
};
const pointStyle = {};
function ritCustomize(input) {
badOnes = [166]; // Nathan's (166) is a duplicate of Ben and Jerry's
for (let i = input.length - 1; i >= 0; i--) {
if (badOnes.indexOf(input[i].mdo_id) >= 0) {
input.splice(i, 1);
} }
).addTo(map);
// var CartoDB_PositronNoLabels = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', {
// attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
// subdomains: 'abcd',
// maxZoom: 20
// }).addTo(map); // good hacky filter: invert(100%) hue-rotate(180deg) brightness(100%) contrast(100%);
// var CartoDB_Positron = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
// attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
// subdomains: 'abcd',
// maxZoom: 20
// }).addTo(map);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
} }
return input;
}
function shuffle(array) { // Unused: "Campus", "Gleason_Engineering_Student_Area"
let currentIndex = array.length, const no_mdo_ids = {
randomIndex; Library_A_Level: { type: "Point", coordinates: [-77.676355, 43.083974] },
Library_1st_Floor: { type: "Point", coordinates: [-77.676355, 43.083874] },
Library_2nd_Floor: { type: "Point", coordinates: [-77.676355, 43.083774] },
Library_3rd_Floor: { type: "Point", coordinates: [-77.676355, 43.083674] },
Library_4th_Floor: { type: "Point", coordinates: [-77.676355, 43.083574] },
Ross_Hall: { type: "Point", coordinates: [-77.677937, 43.082379] },
Gordon_Field_House: { type: "Point", coordinates: [-77.671725, 43.085149] },
Golisano_Institute_for_Sustainability_Lobby: {
type: "Point",
coordinates: [-77.681365, 43.085376],
},
};
// While there remain elements to shuffle. function ritCustomizeCoords(input) {
while (currentIndex > 0) { try {
// Pick a remaining element. if (input.properties.name == "Beanz") {
randomIndex = Math.floor(Math.random() * currentIndex); input.geometry.coordinates = [-77.66904, 43.083876];
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
}
return array;
}
function setMarker(attrs, ref) {
// let red = parseInt("ff", 16);
// let green = parseInt("78", 16);
// let style = {"fillColor": `#${red.toString(16)}${green.toString(16)}00`};
let ratio = attrs.properties.count / attrs.properties.capacity;
ratio = ratio > 1 ? 1 : ratio;
let red = 255 * ratio;
let style = { fillColor: `rgba(${red}, 0, 0, ${ratio})` };
ref.setStyle(style);
ref.bindPopup(
`${attrs.properties.name}<br />Current Occupation: ${attrs.properties.count}`
);
}
function onEachFeature(feature, layer) {
// does this feature have a property named popupContent?
if (!feature.properties || !feature.properties.name) {
return;
}
setMarker(feature, layer);
}
const polyStyle = {
color: "#ff7800",
weight: 5,
opacity: 0.65,
};
const geojsonMarkerOptions = {
radius: 8,
fillColor: "#ff7800",
color: "#ff7800",
weight: 3,
opacity: 1,
fillOpacity: 1,
};
const pointStyle = {};
function ritCustomize(input) {
badOnes = [166]; // Nathan's (166) is a duplicate of Ben and Jerry's
for (let i = input.length - 1; i >= 0; i--) {
if (badOnes.indexOf(input[i].mdo_id) >= 0) {
input.splice(i, 1);
}
} }
return input; return input;
} } catch {}
try {
// Unused: "Campus", "Gleason_Engineering_Student_Area" if (no_mdo_ids[input.location] == undefined) return;
const no_mdo_ids = { let geojsonObj = {
Library_A_Level: { type: "Point", coordinates: [-77.676355, 43.083974] }, geometry: no_mdo_ids[input.location],
Library_1st_Floor: { type: "Point", coordinates: [-77.676355, 43.083874] }, properties: {
Library_2nd_Floor: { type: "Point", coordinates: [-77.676355, 43.083774] }, mdo_id: input.location,
Library_3rd_Floor: { type: "Point", coordinates: [-77.676355, 43.083674] }, name: input.location.replaceAll("_", " "),
Library_4th_Floor: { type: "Point", coordinates: [-77.676355, 43.083574] },
Ross_Hall: { type: "Point", coordinates: [-77.677937, 43.082379] },
Gordon_Field_House: { type: "Point", coordinates: [-77.671725, 43.085149] },
Golisano_Institute_for_Sustainability_Lobby: {
type: "Point",
coordinates: [-77.681365, 43.085376],
},
};
function ritCustomizeCoords(input) {
try {
if (input.properties.name == "Beanz") {
input.geometry.coordinates = [-77.66904, 43.083876];
}
return input;
} catch {}
try {
if (no_mdo_ids[input.location] == undefined) return;
let geojsonObj = {
geometry: no_mdo_ids[input.location],
properties: {
mdo_id: input.location,
name: input.location.replaceAll("_", " "),
},
type: "Feature",
};
return geojsonObj;
} catch {}
}
let pts;
const densityMapUrl = "/hotspotsrit" // https://maps.rit.edu/proxySearch/densityMapDetail.php?mdo=1
async function init() {
let counts = fetch(densityMapUrl + "/cached");
let locations = fetch(
"https://maps.rit.edu/proxySearch/locations.search.php"
);
counts = Object.values(await (await counts).json());
counts = ritCustomize(counts);
locations = await (await locations).json();
pts = {};
locations.forEach((x) => {
for (let i = 0; i < counts.length; i++) {
if (counts[i].mdo_id == x.properties.mdo_id) {
x.properties.count = counts[i].count;
x.properties.capacity = counts[i].max_occ == null ? 100 : counts[i].max_occ;
x = ritCustomizeCoords(x);
pts[x.properties.mdo_id] = x;
break;
}
}
});
counts.forEach((x) => {
if (pts[x.mdo_id] == undefined) {
let geojson = ritCustomizeCoords(x);
if (geojson !== undefined) {
geojson.properties.count = x.count;
geojson.properties.capacity = x.max_occ == null ? 100 : x.max_occ;
pts[x.location] = geojson;
}
}
});
let ptsLayer = L.geoJSON(Object.values(pts), {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
},
style: function (feature) {
switch (feature.geometry.type) {
case "Polygon":
return polyStyle;
case "Point":
return pointStyle;
}
},
onEachFeature: onEachFeature,
}).addTo(map);
const features = ptsLayer.getLayers();
for (let i = 0; i < features.length; i++) {
const key = features[i].feature.properties.mdo_id;
pts[key].properties.reference = features[i];
}
}
// let laid = L.geoJson(pts).addTo(map)
// laid.remove()
let bullets = L.layerGroup([]);
async function shootVector(
from,
to,
{ speed = 500, color = null, onlyAnimate = true, trail = true } = {}
) {
options = {
onlyAnimate: onlyAnimate,
animate: {
duration: speed,
}, },
type: "Feature",
}; };
if (color) options.color = color; return geojsonObj;
const fromC = getCoordArray(from); } catch {}
const toC = getCoordArray(to); }
arcGen(fromC, toC, (options = options));
if (trail) { let pts;
options["color"] = "rgba(190, 95, 0, 0.2)"; let nodePane = map.createPane("nodePane");
options.fade = true; nodePane.style.zIndex = "600";
options.fadeSpeed = 60000 * 15; let nodeGroup;
arcGen(fromC, toC, (options = options)); const densityMapUrl = "/hotspotsrit"; // https://maps.rit.edu/proxySearch/densityMapDetail.php?mdo=1
}
}
function getCoordArray(ref) { async function init(legend = false) {
if (ref.properties == undefined) return ref; let counts = fetch(densityMapUrl + "/cached");
let coords;
try {
coords = ref.properties.reference.getLatLng();
} catch {
coords = ref.properties.reference.getBounds().getCenter();
}
return [coords.lat, coords.lng];
}
function arcGen(latlng1, latlng2, options = {}) { let locations = fetch(
var latlngs = []; "https://maps.rit.edu/proxySearch/locations.search.php"
);
var offsetX = latlng2[1] - latlng1[1], counts = Object.values(await (await counts).json());
offsetY = latlng2[0] - latlng1[0]; counts = ritCustomize(counts);
locations = await (await locations).json();
var r = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2)),
theta = Math.atan2(offsetY, offsetX);
var thetaOffset = 3.14 / 10;
var r2 = r / 2 / Math.cos(thetaOffset),
theta2 = theta + thetaOffset;
var midpointX = r2 * Math.cos(theta2) + latlng1[1],
midpointY = r2 * Math.sin(theta2) + latlng1[0];
var midpointLatLng = [midpointY, midpointX];
latlngs.push(latlng1, midpointLatLng, latlng2);
var pathDefaults = {
color: "#b35900",
weight: 3,
animate: 500,
hasBalls: true,
};
let pathOptions = Object.assign(pathDefaults, options);
var curvedPath = L.curve(
["M", latlng1, "Q", midpointLatLng, latlng2],
pathOptions
).addTo(map);
return curvedPath;
}
function calcDistances(nodes) {
nodes.forEach((x) => {
x.properties.distances = {};
const coords1 = getCoordArray(x);
nodes.forEach((y) => {
const coords2 = getCoordArray(y);
x.properties.distances[y.properties.mdo_id] = Math.sqrt(
Math.pow(coords1[0] - coords2[0], 2) +
Math.pow(coords1[1] - coords2[1], 2)
);
});
});
}
const space = [43.09224, -77.674799];
async function getUpdate() {
console.log("Updating Occupancy Matrix");
let counts = await fetch(densityMapUrl + "/current");
counts = await counts.json();
pts = {};
locations.forEach((x) => {
for (let i = 0; i < counts.length; i++) { for (let i = 0; i < counts.length; i++) {
const pt = if (counts[i].mdo_id == x.properties.mdo_id) {
counts[i].mdo_id == null x.properties.count = counts[i].count;
? pts[counts[i].location] x.properties.capacity =
: pts[counts[i].mdo_id]; counts[i].max_occ == null ? 100 : counts[i].max_occ;
if (pt == undefined) continue; x = ritCustomizeCoords(x);
pt.properties.diff = counts[i].count - pt.properties.count; pts[x.properties.mdo_id] = x;
pt.properties.count = counts[i].count; break;
setMarker(pt, pt.properties.reference);
}
let shots = getShots(Object.values(pts));
shots = shuffle(shuffle(shots));
const timeBetween = (60000 * 5 + 1) / shots.length;
// randomize time delay
let timeDelay = [];
shots.forEach(() => {
timeDelay.push(Math.random());
});
const interval = 60000 * 5; // 5 minute delay
for (let i = 0; i < timeDelay.length; i++) {
timeDelay[i] = timeDelay[i] * interval;
}
console.log(
`Shot total for next 5 minutes: ${shots.length} - ${
timeBetween / 1000
} second intervals`
);
for (let i = 0; i < shots.length; i++) {
loadShot(shots[i], timeDelay[i], { trail: true });
}
}
async function loadShot(shot, delay, { trail = false } = {}) {
await sleep(delay);
shootVector(shot[0], shot[1], { trail: trail });
}
function findOneShot(nodes, target) {
let sortedByDistance = nodes.sort((a, b) => {
return (
target.properties.distances[a.properties.mdo_id] -
target.properties.distances[b.properties.mdo_id]
);
});
const sign = target.properties.diff > 0;
for (let x = 1; x < sortedByDistance.length; x++) {
if (sortedByDistance[x].properties.diff > 0 !== sign) {
return sortedByDistance[x];
} }
} }
}
function getShots(nodes) {
let noChange = false;
let shots = [];
let sourcesAndSinks = nodes.filter((x) => {
return x.properties.diff !== 0;
});
let i;
while (!noChange && sourcesAndSinks.length > 0) {
noChange = true;
// sourcesAndSinks.forEach((x) => {
// x.properties.changed = false;
// });
// for (let i = sourcesAndSinks.length - 1; i >= 0; i--) {
// try {
// if (sourcesAndSinks[i].properties.changed) continue; // this node is a prior recipient this iteration
// } catch {
// continue;
// }
let sorted = sourcesAndSinks.sort((a, b) => {
Math.abs(a.properties.diff) - Math.abs(b.properties.diff);
});
i = sourcesAndSinks.indexOf(sorted[0]);
let recipient = findOneShot(sourcesAndSinks, sourcesAndSinks[i]);
if (recipient) {
let shotArr;
if (sourcesAndSinks[i].properties.diff > 0) {
shotArr = [sourcesAndSinks[i], recipient];
sourcesAndSinks[i].properties.diff--;
recipient.properties.diff++;
} else {
shotArr = [recipient, sourcesAndSinks[i]];
sourcesAndSinks[i].properties.diff++;
recipient.properties.diff--;
}
shots.push(shotArr);
noChange = false;
sourcesAndSinks[i].properties.changed = true;
recipient.properties.changed = true;
let tmpRef = recipient;
if (sourcesAndSinks[i].properties.diff == 0) {
sourcesAndSinks.splice(i, 1);
}
if (
sourcesAndSinks[sourcesAndSinks.indexOf(tmpRef)].properties.diff == 0
) {
sourcesAndSinks.splice(recipient, 1);
}
}
// }
}
// if no more people on campus, get them from space
sourcesAndSinks.forEach((x) => {
while (x.properties.diff > 0) {
shots.push([x, space]);
x.properties.diff--;
}
while (x.properties.diff < 0) {
shots.push([space, x]);
x.properties.diff++;
}
});
return shots;
}
init().then(() => {
// map.on("click", () => {
// shootVector(pts[2], pts[8]);
// });
// shootVector(pts[0], pts[1], {speed: 500});
calcDistances(Object.values(pts));
getUpdate()
setInterval(getUpdate, 60000 * 5);
}); });
counts.forEach((x) => {
if (pts[x.mdo_id] == undefined) {
let geojson = ritCustomizeCoords(x);
if (geojson !== undefined) {
geojson.properties.count = x.count;
geojson.properties.capacity = x.max_occ == null ? 100 : x.max_occ;
pts[x.location] = geojson;
}
}
});
nodeGroup = L.geoJSON(Object.values(pts), {
pane: "nodePane",
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, geojsonMarkerOptions);
},
style: function (feature) {
switch (feature.geometry.type) {
case "Polygon":
return polyStyle;
case "Point":
return pointStyle;
}
},
onEachFeature: onEachFeature,
});
nodeGroup.addTo(map);
nodeGroup.bringToFront();
const features = nodeGroup.getLayers();
for (let i = 0; i < features.length; i++) {
const key = features[i].feature.properties.mdo_id;
pts[key].properties.reference = features[i];
}
legend ? makeLegend() : null;
}
function makeLegend() {
let legend = L.control({ position: "bottomright" });
legend.onAdd = function (mapref) {
let div = L.DomUtil.create("div", "info legend");
div.innerHTML = "<div id='legendOccGrad'>Occupancy / Max Occupancy Gradient</div>"
div.innerHTML +=
`<p>Markers represent locations where data is collected<br />
Vectors represent the migration of aggregate occupation</br>
vectors to/from nodeless points involve locations not tracked by RIT</p>`;
return div;
};
let statControl = L.control({ position: "topright" });
statControl.onAdd = function (mapref) {
let div = L.DomUtil.create("div", "info legend");
div.innerHTML = "<p>Occupancy is updated every 5 minutes<br /><br /><span id='shotCounter'></span><br />Next update in <span id='countdownClock'></span> seconds</p>"
return div;
//
};
statControl.addTo(map);
legend.addTo(map);
}
function updateLegend(shotcount=undefined){
document.getElementById('shotCounter').textContent = `Previous update created ${shotcount} migration${shotcount == 1 ? "" : "s"}`;
}
const space_coords = [43.09224, -77.674799];
const UC_coords = [43.080361, -77.683296];
const perkins_coords = [43.08616, -77.661796];
function setSpace(features) {
features.forEach((x) => {
const centroid = getCoordArray(x);
if (centroid[1] > -77.673157) {
x.properties.space = perkins_coords;
} else if (centroid[1] < -77.677503 && centroid[0] < 43.08395) {
x.properties.space = UC_coords;
} else {
x.properties.space = space_coords;
}
});
}
let bullets = L.layerGroup([]);
async function shootVector(
from,
to,
{ speed = 500, color = null, onlyAnimate = true, trail = true } = {}
) {
options = {
onlyAnimate: onlyAnimate,
animate: {
duration: speed,
},
};
if (color) options.color = color;
const fromC = getCoordArray(from);
const toC = getCoordArray(to);
arcGen(fromC, toC, (options = options));
if (trail) {
options["color"] = "rgba(190, 95, 0, 0.2)";
options.fade = true;
options.fadeSpeed = 60000 * 15;
arcGen(fromC, toC, (options = options));
}
}
function getCoordArray(ref) {
if (ref.properties == undefined) return ref;
let coords;
try {
coords = ref.properties.reference.getLatLng();
} catch {
coords = ref.properties.reference.getBounds().getCenter();
}
return [coords.lat, coords.lng];
}
function arcGen(latlng1, latlng2, options = {}) {
var latlngs = [];
var offsetX = latlng2[1] - latlng1[1],
offsetY = latlng2[0] - latlng1[0];
var r = Math.sqrt(Math.pow(offsetX, 2) + Math.pow(offsetY, 2)),
theta = Math.atan2(offsetY, offsetX);
var thetaOffset = 3.14 / 10;
var r2 = r / 2 / Math.cos(thetaOffset),
theta2 = theta + thetaOffset;
var midpointX = r2 * Math.cos(theta2) + latlng1[1],
midpointY = r2 * Math.sin(theta2) + latlng1[0];
var midpointLatLng = [midpointY, midpointX];
latlngs.push(latlng1, midpointLatLng, latlng2);
var pathDefaults = {
color: "#b35900",
weight: 3,
animate: 500,
hasBalls: true,
};
let pathOptions = Object.assign(pathDefaults, options);
var curvedPath = L.curve(
["M", latlng1, "Q", midpointLatLng, latlng2],
pathOptions
).addTo(map);
return curvedPath;
}
function calcDistances(nodes) {
nodes.forEach((x) => {
x.properties.distances = {};
const coords1 = getCoordArray(x);
nodes.forEach((y) => {
const coords2 = getCoordArray(y);
x.properties.distances[y.properties.mdo_id] = Math.sqrt(
Math.pow(coords1[0] - coords2[0], 2) +
Math.pow(coords1[1] - coords2[1], 2)
);
});
});
}
let countdownTo;
function updateCountdown() {
const now = new Date().getTime();
document.getElementById('countdownClock').textContent = Math.round((countdownTo - now) / 1000);
}
async function getUpdate() {
console.log("Updating Occupancy Matrix");
countdownTo = new Date().getTime() + 5 * 60 * 1000;
let counts = await fetch(densityMapUrl + "/current");
counts = await counts.json();
for (let i = 0; i < counts.length; i++) {
const pt =
counts[i].mdo_id == null
? pts[counts[i].location]
: pts[counts[i].mdo_id];
if (pt == undefined) continue;
pt.properties.diff = counts[i].count - pt.properties.count;
pt.properties.count = counts[i].count;
setMarker(pt, pt.properties.reference);
}
let shots = getShots(Object.values(pts));
shots = shuffle(shuffle(shots));
const timeBetween = (60000 * 5 + 1) / shots.length;
// randomize time delay
let timeDelay = [];
shots.forEach(() => {
timeDelay.push(Math.random());
});
const interval = 60000 * 5; // 5 minute delay
for (let i = 0; i < timeDelay.length; i++) {
timeDelay[i] = timeDelay[i] * interval;
}
console.log(
`Shot total for next 5 minutes: ${shots.length} - ${
timeBetween / 1000
} second intervals`
);
updateLegend(shots.length);
for (let i = 0; i < shots.length; i++) {
loadShot(shots[i], timeDelay[i], { trail: true });
}
}
async function loadShot(shot, delay, { trail = false } = {}) {
await sleep(delay);
shootVector(shot[0], shot[1], { trail: trail });
}
function findOneShot(nodes, target) {
let sortedByDistance = nodes.sort((a, b) => {
return (
target.properties.distances[a.properties.mdo_id] -
target.properties.distances[b.properties.mdo_id]
);
});
const sign = target.properties.diff > 0;
for (let x = 1; x < sortedByDistance.length; x++) {
if (sortedByDistance[x].properties.diff > 0 !== sign) {
return sortedByDistance[x];
}
}
}
function getShots(nodes) {
let noChange = false;
let shots = [];
let sourcesAndSinks = nodes.filter((x) => {
return x.properties.diff !== 0;
});
let i;
while (!noChange && sourcesAndSinks.length > 0) {
noChange = true;
// sourcesAndSinks.forEach((x) => {
// x.properties.changed = false;
// });
// for (let i = sourcesAndSinks.length - 1; i >= 0; i--) {
// try {
// if (sourcesAndSinks[i].properties.changed) continue; // this node is a prior recipient this iteration
// } catch {
// continue;
// }
let sorted = sourcesAndSinks.sort((a, b) => {
Math.abs(a.properties.diff) - Math.abs(b.properties.diff);
});
i = sourcesAndSinks.indexOf(sorted[0]);
let recipient = findOneShot(sourcesAndSinks, sourcesAndSinks[i]);
if (recipient) {
let shotArr;
if (sourcesAndSinks[i].properties.diff > 0) {
shotArr = [sourcesAndSinks[i], recipient];
sourcesAndSinks[i].properties.diff--;
recipient.properties.diff++;
} else {
shotArr = [recipient, sourcesAndSinks[i]];
sourcesAndSinks[i].properties.diff++;
recipient.properties.diff--;
}
shots.push(shotArr);
noChange = false;
sourcesAndSinks[i].properties.changed = true;
recipient.properties.changed = true;
let tmpRef = recipient;
if (sourcesAndSinks[i].properties.diff == 0) {
sourcesAndSinks.splice(i, 1);
}
if (
sourcesAndSinks[sourcesAndSinks.indexOf(tmpRef)].properties.diff == 0
) {
sourcesAndSinks.splice(recipient, 1);
}
}
// }
}
// if no more people on campus, get them from space
sourcesAndSinks.forEach((x) => {
while (x.properties.diff > 0) {
shots.push([x, x.properties.space]);
x.properties.diff--;
}
while (x.properties.diff < 0) {
shots.push([x.properties.space, x]);
x.properties.diff++;
}
});
return shots;
}
const useLegend = window.location.pathname.replaceAll("/", "") == "hotspots"
init(useLegend).then(() => {
// map.on("click", () => {
// shootVector(pts[2], pts[8]);
// });
// shootVector(pts[0], pts[1], {speed: 500});
let ptsarr = Object.values(pts);
calcDistances(ptsarr);
setSpace(ptsarr);
getUpdate();
setInterval(updateCountdown, 1000);
setInterval(getUpdate, 60000 * 5);
});

View File

@@ -1,5 +1,33 @@
{ {
"Lower 48 Alt. Energy Map": { "RIT Hotspots": {
"status": "WIP",
"classes": "pinned geospacial programming",
"bgi": "hotspotsrit.png",
"content": "Live crowd migration map using RIT occupancy data",
"links": [
[
"github", "https://github.com/asimonson1125/hotspotsrit", "git repo"
],
[
"globe", "https://asimonson.com/hotspots", "demo"
]
]
},
"LogicFlow": {
"status": "incomplete",
"classes": "programming",
"bgi": "logicflow.jpg",
"content": "Translate paragraphs to logical flowcharts, powered by ChatGPT Winner of CSHacks' Best Use of AI by Paychex",
"links": [
[
"github", "https://github.com/asimonson1125/LogicFlow", "git repo"
],
[
"globe", "https://devpost.com/software/logicflow", "Hackathon listing"
]
]
},
"Alternative Energy Map": {
"status": "complete", "status": "complete",
"classes": "pinned geospacial", "classes": "pinned geospacial",
"bgi": "geovisF.png", "bgi": "geovisF.png",
@@ -14,7 +42,7 @@
}, },
"OccupyRIT": { "OccupyRIT": {
"status": "WIP", "status": "WIP",
"classes": "pinned programming", "classes": "programming",
"bgi": "occupyRIT.png", "bgi": "occupyRIT.png",
"content": "Collects RIT Gym Occupancy data, determining busiest workout times", "content": "Collects RIT Gym Occupancy data, determining busiest workout times",
"links": [ "links": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB