smaller logo
@@ -1,171 +0,0 @@
|
||||
/* quarto syntax highlight colors */
|
||||
:root {
|
||||
--quarto-hl-ot-color: #003B4F;
|
||||
--quarto-hl-at-color: #657422;
|
||||
--quarto-hl-ss-color: #20794D;
|
||||
--quarto-hl-an-color: #5E5E5E;
|
||||
--quarto-hl-fu-color: #4758AB;
|
||||
--quarto-hl-st-color: #20794D;
|
||||
--quarto-hl-cf-color: #003B4F;
|
||||
--quarto-hl-op-color: #5E5E5E;
|
||||
--quarto-hl-er-color: #AD0000;
|
||||
--quarto-hl-bn-color: #AD0000;
|
||||
--quarto-hl-al-color: #AD0000;
|
||||
--quarto-hl-va-color: #111111;
|
||||
--quarto-hl-bu-color: inherit;
|
||||
--quarto-hl-ex-color: inherit;
|
||||
--quarto-hl-pp-color: #AD0000;
|
||||
--quarto-hl-in-color: #5E5E5E;
|
||||
--quarto-hl-vs-color: #20794D;
|
||||
--quarto-hl-wa-color: #5E5E5E;
|
||||
--quarto-hl-do-color: #5E5E5E;
|
||||
--quarto-hl-im-color: #00769E;
|
||||
--quarto-hl-ch-color: #20794D;
|
||||
--quarto-hl-dt-color: #AD0000;
|
||||
--quarto-hl-fl-color: #AD0000;
|
||||
--quarto-hl-co-color: #5E5E5E;
|
||||
--quarto-hl-cv-color: #5E5E5E;
|
||||
--quarto-hl-cn-color: #8f5902;
|
||||
--quarto-hl-sc-color: #5E5E5E;
|
||||
--quarto-hl-dv-color: #AD0000;
|
||||
--quarto-hl-kw-color: #003B4F;
|
||||
}
|
||||
|
||||
/* other quarto variables */
|
||||
:root {
|
||||
--quarto-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
pre > code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code.sourceCode > span {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
div.sourceCode,
|
||||
div.sourceCode pre.sourceCode {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code span.ot {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code span.at {
|
||||
color: #657422;
|
||||
}
|
||||
|
||||
code span.ss {
|
||||
color: #20794D;
|
||||
}
|
||||
|
||||
code span.an {
|
||||
color: #5E5E5E;
|
||||
}
|
||||
|
||||
code span.fu {
|
||||
color: #4758AB;
|
||||
}
|
||||
|
||||
code span.st {
|
||||
color: #20794D;
|
||||
}
|
||||
|
||||
code span.cf {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
code span.op {
|
||||
color: #5E5E5E;
|
||||
}
|
||||
|
||||
code span.er {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.bn {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.al {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.va {
|
||||
color: #111111;
|
||||
}
|
||||
|
||||
code span.pp {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.in {
|
||||
color: #5E5E5E;
|
||||
}
|
||||
|
||||
code span.vs {
|
||||
color: #20794D;
|
||||
}
|
||||
|
||||
code span.wa {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code span.do {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code span.im {
|
||||
color: #00769E;
|
||||
}
|
||||
|
||||
code span.ch {
|
||||
color: #20794D;
|
||||
}
|
||||
|
||||
code span.dt {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.fl {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.co {
|
||||
color: #5E5E5E;
|
||||
}
|
||||
|
||||
code span.cv {
|
||||
color: #5E5E5E;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
code span.cn {
|
||||
color: #8f5902;
|
||||
}
|
||||
|
||||
code span.sc {
|
||||
color: #5E5E5E;
|
||||
}
|
||||
|
||||
code span.dv {
|
||||
color: #AD0000;
|
||||
}
|
||||
|
||||
code span.kw {
|
||||
color: #003B4F;
|
||||
}
|
||||
|
||||
.prevent-inlining {
|
||||
content: "</";
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=debc5d5d77c3f9108843748ff7464032.css.map */
|
||||
@@ -1,760 +0,0 @@
|
||||
const sectionChanged = new CustomEvent("quarto-sectionChanged", {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
composed: false,
|
||||
});
|
||||
|
||||
window.document.addEventListener("DOMContentLoaded", function (_event) {
|
||||
const tocEl = window.document.querySelector('nav.toc-active[role="doc-toc"]');
|
||||
const sidebarEl = window.document.getElementById("quarto-sidebar");
|
||||
const leftTocEl = window.document.getElementById("quarto-sidebar-toc-left");
|
||||
const marginSidebarEl = window.document.getElementById(
|
||||
"quarto-margin-sidebar"
|
||||
);
|
||||
// function to determine whether the element has a previous sibling that is active
|
||||
const prevSiblingIsActiveLink = (el) => {
|
||||
const sibling = el.previousElementSibling;
|
||||
if (sibling && sibling.tagName === "A") {
|
||||
return sibling.classList.contains("active");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// fire slideEnter for bootstrap tab activations (for htmlwidget resize behavior)
|
||||
function fireSlideEnter(e) {
|
||||
const event = window.document.createEvent("Event");
|
||||
event.initEvent("slideenter", true, true);
|
||||
window.document.dispatchEvent(event);
|
||||
}
|
||||
const tabs = window.document.querySelectorAll('a[data-bs-toggle="tab"]');
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener("shown.bs.tab", fireSlideEnter);
|
||||
});
|
||||
|
||||
// Track scrolling and mark TOC links as active
|
||||
// get table of contents and sidebar (bail if we don't have at least one)
|
||||
const tocLinks = tocEl
|
||||
? [...tocEl.querySelectorAll("a[data-scroll-target]")]
|
||||
: [];
|
||||
const makeActive = (link) => tocLinks[link].classList.add("active");
|
||||
const removeActive = (link) => tocLinks[link].classList.remove("active");
|
||||
const removeAllActive = () =>
|
||||
[...Array(tocLinks.length).keys()].forEach((link) => removeActive(link));
|
||||
|
||||
// activate the anchor for a section associated with this TOC entry
|
||||
tocLinks.forEach((link) => {
|
||||
link.addEventListener("click", () => {
|
||||
if (link.href.indexOf("#") !== -1) {
|
||||
const anchor = link.href.split("#")[1];
|
||||
const heading = window.document.querySelector(
|
||||
`[data-anchor-id=${anchor}]`
|
||||
);
|
||||
if (heading) {
|
||||
// Add the class
|
||||
heading.classList.add("reveal-anchorjs-link");
|
||||
|
||||
// function to show the anchor
|
||||
const handleMouseout = () => {
|
||||
heading.classList.remove("reveal-anchorjs-link");
|
||||
heading.removeEventListener("mouseout", handleMouseout);
|
||||
};
|
||||
|
||||
// add a function to clear the anchor when the user mouses out of it
|
||||
heading.addEventListener("mouseout", handleMouseout);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const sections = tocLinks.map((link) => {
|
||||
const target = link.getAttribute("data-scroll-target");
|
||||
if (target.startsWith("#")) {
|
||||
return window.document.getElementById(decodeURI(`${target.slice(1)}`));
|
||||
} else {
|
||||
return window.document.querySelector(decodeURI(`${target}`));
|
||||
}
|
||||
});
|
||||
|
||||
const sectionMargin = 200;
|
||||
let currentActive = 0;
|
||||
// track whether we've initialized state the first time
|
||||
let init = false;
|
||||
|
||||
const updateActiveLink = () => {
|
||||
// The index from bottom to top (e.g. reversed list)
|
||||
let sectionIndex = -1;
|
||||
if (
|
||||
window.innerHeight + window.pageYOffset >=
|
||||
window.document.body.offsetHeight
|
||||
) {
|
||||
sectionIndex = 0;
|
||||
} else {
|
||||
sectionIndex = [...sections].reverse().findIndex((section) => {
|
||||
if (section) {
|
||||
return window.pageYOffset >= section.offsetTop - sectionMargin;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (sectionIndex > -1) {
|
||||
const current = sections.length - sectionIndex - 1;
|
||||
if (current !== currentActive) {
|
||||
removeAllActive();
|
||||
currentActive = current;
|
||||
makeActive(current);
|
||||
if (init) {
|
||||
window.dispatchEvent(sectionChanged);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inHiddenRegion = (top, bottom, hiddenRegions) => {
|
||||
for (const region of hiddenRegions) {
|
||||
if (top <= region.bottom && bottom >= region.top) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const categorySelector = "header.quarto-title-block .quarto-category";
|
||||
const activateCategories = (href) => {
|
||||
// Find any categories
|
||||
// Surround them with a link pointing back to:
|
||||
// #category=Authoring
|
||||
try {
|
||||
const categoryEls = window.document.querySelectorAll(categorySelector);
|
||||
for (const categoryEl of categoryEls) {
|
||||
const categoryText = categoryEl.textContent;
|
||||
if (categoryText) {
|
||||
const link = `${href}#category=${encodeURIComponent(categoryText)}`;
|
||||
const linkEl = window.document.createElement("a");
|
||||
linkEl.setAttribute("href", link);
|
||||
for (const child of categoryEl.childNodes) {
|
||||
linkEl.append(child);
|
||||
}
|
||||
categoryEl.appendChild(linkEl);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
};
|
||||
function hasTitleCategories() {
|
||||
return window.document.querySelector(categorySelector) !== null;
|
||||
}
|
||||
|
||||
function offsetRelativeUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
return offset ? offset + url : url;
|
||||
}
|
||||
|
||||
function offsetAbsoluteUrl(url) {
|
||||
const offset = getMeta("quarto:offset");
|
||||
const baseUrl = new URL(offset, window.location);
|
||||
|
||||
const projRelativeUrl = url.replace(baseUrl, "");
|
||||
if (projRelativeUrl.startsWith("/")) {
|
||||
return projRelativeUrl;
|
||||
} else {
|
||||
return "/" + projRelativeUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// read a meta tag value
|
||||
function getMeta(metaName) {
|
||||
const metas = window.document.getElementsByTagName("meta");
|
||||
for (let i = 0; i < metas.length; i++) {
|
||||
if (metas[i].getAttribute("name") === metaName) {
|
||||
return metas[i].getAttribute("content");
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
async function findAndActivateCategories() {
|
||||
const currentPagePath = offsetAbsoluteUrl(window.location.href);
|
||||
const response = await fetch(offsetRelativeUrl("listings.json"));
|
||||
if (response.status == 200) {
|
||||
return response.json().then(function (listingPaths) {
|
||||
const listingHrefs = [];
|
||||
for (const listingPath of listingPaths) {
|
||||
const pathWithoutLeadingSlash = listingPath.listing.substring(1);
|
||||
for (const item of listingPath.items) {
|
||||
if (
|
||||
item === currentPagePath ||
|
||||
item === currentPagePath + "index.html"
|
||||
) {
|
||||
// Resolve this path against the offset to be sure
|
||||
// we already are using the correct path to the listing
|
||||
// (this adjusts the listing urls to be rooted against
|
||||
// whatever root the page is actually running against)
|
||||
const relative = offsetRelativeUrl(pathWithoutLeadingSlash);
|
||||
const baseUrl = window.location;
|
||||
const resolvedPath = new URL(relative, baseUrl);
|
||||
listingHrefs.push(resolvedPath.pathname);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const nearestListing = findNearestParentListing(
|
||||
offsetAbsoluteUrl(window.location.pathname),
|
||||
listingHrefs
|
||||
);
|
||||
if (nearestListing) {
|
||||
activateCategories(nearestListing);
|
||||
} else {
|
||||
// See if the referrer is a listing page for this item
|
||||
const referredRelativePath = offsetAbsoluteUrl(document.referrer);
|
||||
const referrerListing = listingHrefs.find((listingHref) => {
|
||||
const isListingReferrer =
|
||||
listingHref === referredRelativePath ||
|
||||
listingHref === referredRelativePath + "index.html";
|
||||
return isListingReferrer;
|
||||
});
|
||||
|
||||
if (referrerListing) {
|
||||
// Try to use the referrer if possible
|
||||
activateCategories(referrerListing);
|
||||
} else if (listingHrefs.length > 0) {
|
||||
// Otherwise, just fall back to the first listing
|
||||
activateCategories(listingHrefs[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (hasTitleCategories()) {
|
||||
findAndActivateCategories();
|
||||
}
|
||||
|
||||
const findNearestParentListing = (href, listingHrefs) => {
|
||||
if (!href || !listingHrefs) {
|
||||
return undefined;
|
||||
}
|
||||
// Look up the tree for a nearby linting and use that if we find one
|
||||
const relativeParts = href.substring(1).split("/");
|
||||
while (relativeParts.length > 0) {
|
||||
const path = relativeParts.join("/");
|
||||
for (const listingHref of listingHrefs) {
|
||||
if (listingHref.startsWith(path)) {
|
||||
return listingHref;
|
||||
}
|
||||
}
|
||||
relativeParts.pop();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const manageSidebarVisiblity = (el, placeholderDescriptor) => {
|
||||
let isVisible = true;
|
||||
|
||||
return (hiddenRegions) => {
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the last element of the TOC
|
||||
const lastChildEl = el.lastElementChild;
|
||||
|
||||
if (lastChildEl) {
|
||||
// Find the top and bottom o the element that is being managed
|
||||
const elTop = el.offsetTop;
|
||||
const elBottom =
|
||||
elTop + lastChildEl.offsetTop + lastChildEl.offsetHeight;
|
||||
|
||||
// Converts the sidebar to a menu
|
||||
const convertToMenu = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 0;
|
||||
child.style.overflow = "hidden";
|
||||
}
|
||||
|
||||
const toggleContainer = window.document.createElement("div");
|
||||
toggleContainer.style.width = "100%";
|
||||
toggleContainer.classList.add("zindex-over-content");
|
||||
toggleContainer.classList.add("quarto-sidebar-toggle");
|
||||
toggleContainer.classList.add("headroom-target"); // Marks this to be managed by headeroom
|
||||
toggleContainer.id = placeholderDescriptor.id;
|
||||
toggleContainer.style.position = "fixed";
|
||||
|
||||
const toggleIcon = window.document.createElement("i");
|
||||
toggleIcon.classList.add("quarto-sidebar-toggle-icon");
|
||||
toggleIcon.classList.add("bi");
|
||||
toggleIcon.classList.add("bi-caret-down-fill");
|
||||
|
||||
const toggleTitle = window.document.createElement("div");
|
||||
const titleEl = window.document.body.querySelector(
|
||||
placeholderDescriptor.titleSelector
|
||||
);
|
||||
if (titleEl) {
|
||||
toggleTitle.append(titleEl.innerText, toggleIcon);
|
||||
}
|
||||
toggleTitle.classList.add("zindex-over-content");
|
||||
toggleTitle.classList.add("quarto-sidebar-toggle-title");
|
||||
toggleContainer.append(toggleTitle);
|
||||
|
||||
const toggleContents = window.document.createElement("div");
|
||||
toggleContents.classList = el.classList;
|
||||
toggleContents.classList.add("zindex-over-content");
|
||||
toggleContents.classList.add("quarto-sidebar-toggle-contents");
|
||||
for (const child of el.children) {
|
||||
if (child.id === "toc-title") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const clone = child.cloneNode(true);
|
||||
clone.style.opacity = 1;
|
||||
clone.style.display = null;
|
||||
toggleContents.append(clone);
|
||||
}
|
||||
toggleContents.style.height = "0px";
|
||||
toggleContainer.append(toggleContents);
|
||||
el.parentElement.prepend(toggleContainer);
|
||||
|
||||
// Process clicks
|
||||
let tocShowing = false;
|
||||
// Allow the caller to control whether this is dismissed
|
||||
// when it is clicked (e.g. sidebar navigation supports
|
||||
// opening and closing the nav tree, so don't dismiss on click)
|
||||
const clickEl = placeholderDescriptor.dismissOnClick
|
||||
? toggleContainer
|
||||
: toggleTitle;
|
||||
|
||||
const closeToggle = () => {
|
||||
if (tocShowing) {
|
||||
toggleContainer.classList.remove("expanded");
|
||||
toggleContents.style.height = "0px";
|
||||
tocShowing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const positionToggle = () => {
|
||||
// position the element (top left of parent, same width as parent)
|
||||
const elRect = el.getBoundingClientRect();
|
||||
toggleContainer.style.left = `${elRect.left}px`;
|
||||
toggleContainer.style.top = `${elRect.top}px`;
|
||||
toggleContainer.style.width = `${elRect.width}px`;
|
||||
};
|
||||
|
||||
// Get rid of any expanded toggle if the user scrolls
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
closeToggle();
|
||||
}, 50)
|
||||
);
|
||||
|
||||
// Handle positioning of the toggle
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
positionToggle();
|
||||
}, 50)
|
||||
);
|
||||
positionToggle();
|
||||
|
||||
// Process the click
|
||||
clickEl.onclick = () => {
|
||||
if (!tocShowing) {
|
||||
toggleContainer.classList.add("expanded");
|
||||
toggleContents.style.height = null;
|
||||
tocShowing = true;
|
||||
} else {
|
||||
closeToggle();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Converts a sidebar from a menu back to a sidebar
|
||||
const convertToSidebar = () => {
|
||||
for (const child of el.children) {
|
||||
child.style.opacity = 1;
|
||||
child.style.overflow = null;
|
||||
}
|
||||
|
||||
const placeholderEl = window.document.getElementById(
|
||||
placeholderDescriptor.id
|
||||
);
|
||||
if (placeholderEl) {
|
||||
placeholderEl.remove();
|
||||
}
|
||||
|
||||
el.classList.remove("rollup");
|
||||
};
|
||||
|
||||
if (isReaderMode()) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
} else {
|
||||
if (!isVisible) {
|
||||
// If the element is current not visible reveal if there are
|
||||
// no conflicts with overlay regions
|
||||
if (!inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToSidebar();
|
||||
isVisible = true;
|
||||
}
|
||||
} else {
|
||||
// If the element is visible, hide it if it conflicts with overlay regions
|
||||
// and insert a placeholder toggle (or if we're in reader mode)
|
||||
if (inHiddenRegion(elTop, elBottom, hiddenRegions)) {
|
||||
convertToMenu();
|
||||
isVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Find any conflicting margin elements and add margins to the
|
||||
// top to prevent overlap
|
||||
const marginChildren = window.document.querySelectorAll(
|
||||
".column-margin.column-container > * "
|
||||
);
|
||||
let lastBottom = 0;
|
||||
for (const marginChild of marginChildren) {
|
||||
const top = marginChild.getBoundingClientRect().top;
|
||||
if (top < lastBottom) {
|
||||
const margin = lastBottom - top;
|
||||
marginChild.style.marginTop = `${margin}px`;
|
||||
}
|
||||
const styles = window.getComputedStyle(marginChild);
|
||||
const marginTop = parseFloat(styles["marginTop"]);
|
||||
|
||||
lastBottom = top + marginChild.getBoundingClientRect().height + marginTop;
|
||||
}
|
||||
|
||||
// Manage the visibility of the toc and the sidebar
|
||||
const marginScrollVisibility = manageSidebarVisiblity(marginSidebarEl, {
|
||||
id: "quarto-toc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
const sidebarScrollVisiblity = manageSidebarVisiblity(sidebarEl, {
|
||||
id: "quarto-sidebarnav-toggle",
|
||||
titleSelector: ".title",
|
||||
dismissOnClick: false,
|
||||
});
|
||||
let tocLeftScrollVisibility;
|
||||
if (leftTocEl) {
|
||||
tocLeftScrollVisibility = manageSidebarVisiblity(leftTocEl, {
|
||||
id: "quarto-lefttoc-toggle",
|
||||
titleSelector: "#toc-title",
|
||||
dismissOnClick: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Find the first element that uses formatting in special columns
|
||||
const conflictingEls = window.document.body.querySelectorAll(
|
||||
'[class^="column-"], [class*=" column-"], aside, [class*="margin-caption"], [class*=" margin-caption"], [class*="margin-ref"], [class*=" margin-ref"]'
|
||||
);
|
||||
|
||||
// Filter all the possibly conflicting elements into ones
|
||||
// the do conflict on the left or ride side
|
||||
const arrConflictingEls = Array.from(conflictingEls);
|
||||
const leftSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return false;
|
||||
}
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("right") &&
|
||||
!className.endsWith("container") &&
|
||||
className !== "column-margin"
|
||||
);
|
||||
});
|
||||
});
|
||||
const rightSideConflictEls = arrConflictingEls.filter((el) => {
|
||||
if (el.tagName === "ASIDE") {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasMarginCaption = Array.from(el.classList).find((className) => {
|
||||
return className == "margin-caption";
|
||||
});
|
||||
if (hasMarginCaption) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Array.from(el.classList).find((className) => {
|
||||
return (
|
||||
className !== "column-body" &&
|
||||
!className.endsWith("container") &&
|
||||
className.startsWith("column-") &&
|
||||
!className.endsWith("left")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const kOverlapPaddingSize = 10;
|
||||
function toRegions(els) {
|
||||
return els.map((el) => {
|
||||
const top =
|
||||
el.getBoundingClientRect().top +
|
||||
document.documentElement.scrollTop -
|
||||
kOverlapPaddingSize;
|
||||
return {
|
||||
top,
|
||||
bottom: top + el.scrollHeight + 2 * kOverlapPaddingSize,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const hideOverlappedSidebars = () => {
|
||||
marginScrollVisibility(toRegions(rightSideConflictEls));
|
||||
sidebarScrollVisiblity(toRegions(leftSideConflictEls));
|
||||
if (tocLeftScrollVisibility) {
|
||||
tocLeftScrollVisibility(toRegions(leftSideConflictEls));
|
||||
}
|
||||
};
|
||||
|
||||
window.quartoToggleReader = () => {
|
||||
// Applies a slow class (or removes it)
|
||||
// to update the transition speed
|
||||
const slowTransition = (slow) => {
|
||||
const manageTransition = (id, slow) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
if (slow) {
|
||||
el.classList.add("slow");
|
||||
} else {
|
||||
el.classList.remove("slow");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
manageTransition("TOC", slow);
|
||||
manageTransition("quarto-sidebar", slow);
|
||||
};
|
||||
|
||||
const readerMode = !isReaderMode();
|
||||
setReaderModeValue(readerMode);
|
||||
|
||||
// If we're entering reader mode, slow the transition
|
||||
if (readerMode) {
|
||||
slowTransition(readerMode);
|
||||
}
|
||||
highlightReaderToggle(readerMode);
|
||||
hideOverlappedSidebars();
|
||||
|
||||
// If we're exiting reader mode, restore the non-slow transition
|
||||
if (!readerMode) {
|
||||
slowTransition(!readerMode);
|
||||
}
|
||||
};
|
||||
|
||||
const highlightReaderToggle = (readerMode) => {
|
||||
const els = document.querySelectorAll(".quarto-reader-toggle");
|
||||
if (els) {
|
||||
els.forEach((el) => {
|
||||
if (readerMode) {
|
||||
el.classList.add("reader");
|
||||
} else {
|
||||
el.classList.remove("reader");
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setReaderModeValue = (val) => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
window.localStorage.setItem("quarto-reader-mode", val);
|
||||
} else {
|
||||
localReaderMode = val;
|
||||
}
|
||||
};
|
||||
|
||||
const isReaderMode = () => {
|
||||
if (window.location.protocol !== "file:") {
|
||||
return window.localStorage.getItem("quarto-reader-mode") === "true";
|
||||
} else {
|
||||
return localReaderMode;
|
||||
}
|
||||
};
|
||||
let localReaderMode = null;
|
||||
|
||||
// Walk the TOC and collapse/expand nodes
|
||||
// Nodes are expanded if:
|
||||
// - they are top level
|
||||
// - they have children that are 'active' links
|
||||
// - they are directly below an link that is 'active'
|
||||
const walk = (el, depth) => {
|
||||
// Tick depth when we enter a UL
|
||||
if (el.tagName === "UL") {
|
||||
depth = depth + 1;
|
||||
}
|
||||
|
||||
// It this is active link
|
||||
let isActiveNode = false;
|
||||
if (el.tagName === "A" && el.classList.contains("active")) {
|
||||
isActiveNode = true;
|
||||
}
|
||||
|
||||
// See if there is an active child to this element
|
||||
let hasActiveChild = false;
|
||||
for (child of el.children) {
|
||||
hasActiveChild = walk(child, depth) || hasActiveChild;
|
||||
}
|
||||
|
||||
// Process the collapse state if this is an UL
|
||||
if (el.tagName === "UL") {
|
||||
if (depth === 1 || hasActiveChild || prevSiblingIsActiveLink(el)) {
|
||||
el.classList.remove("collapse");
|
||||
} else {
|
||||
el.classList.add("collapse");
|
||||
}
|
||||
|
||||
// untick depth when we leave a UL
|
||||
depth = depth - 1;
|
||||
}
|
||||
return hasActiveChild || isActiveNode;
|
||||
};
|
||||
|
||||
// walk the TOC and expand / collapse any items that should be shown
|
||||
|
||||
if (tocEl) {
|
||||
walk(tocEl, 0);
|
||||
updateActiveLink();
|
||||
}
|
||||
|
||||
// Throttle the scroll event and walk peridiocally
|
||||
window.document.addEventListener(
|
||||
"scroll",
|
||||
throttle(() => {
|
||||
if (tocEl) {
|
||||
updateActiveLink();
|
||||
walk(tocEl, 0);
|
||||
}
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 5)
|
||||
);
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
throttle(() => {
|
||||
if (!isReaderMode()) {
|
||||
hideOverlappedSidebars();
|
||||
}
|
||||
}, 10)
|
||||
);
|
||||
hideOverlappedSidebars();
|
||||
highlightReaderToggle(isReaderMode());
|
||||
});
|
||||
|
||||
// grouped tabsets
|
||||
window.addEventListener("pageshow", (_event) => {
|
||||
function getTabSettings() {
|
||||
const data = localStorage.getItem("quarto-persistent-tabsets-data");
|
||||
if (!data) {
|
||||
localStorage.setItem("quarto-persistent-tabsets-data", "{}");
|
||||
return {};
|
||||
}
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
}
|
||||
|
||||
function setTabSettings(data) {
|
||||
localStorage.setItem(
|
||||
"quarto-persistent-tabsets-data",
|
||||
JSON.stringify(data)
|
||||
);
|
||||
}
|
||||
|
||||
function setTabState(groupName, groupValue) {
|
||||
const data = getTabSettings();
|
||||
data[groupName] = groupValue;
|
||||
setTabSettings(data);
|
||||
}
|
||||
|
||||
function toggleTab(tab, active) {
|
||||
const tabPanelId = tab.getAttribute("aria-controls");
|
||||
const tabPanel = document.getElementById(tabPanelId);
|
||||
if (active) {
|
||||
tab.classList.add("active");
|
||||
tabPanel.classList.add("active");
|
||||
} else {
|
||||
tab.classList.remove("active");
|
||||
tabPanel.classList.remove("active");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAll(selectedGroup, selectorsToSync) {
|
||||
for (const [thisGroup, tabs] of Object.entries(selectorsToSync)) {
|
||||
const active = selectedGroup === thisGroup;
|
||||
for (const tab of tabs) {
|
||||
toggleTab(tab, active);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findSelectorsToSyncByLanguage() {
|
||||
const result = {};
|
||||
const tabs = Array.from(
|
||||
document.querySelectorAll(`div[data-group] a[id^='tabset-']`)
|
||||
);
|
||||
for (const item of tabs) {
|
||||
const div = item.parentElement.parentElement.parentElement;
|
||||
const group = div.getAttribute("data-group");
|
||||
if (!result[group]) {
|
||||
result[group] = {};
|
||||
}
|
||||
const selectorsToSync = result[group];
|
||||
const value = item.innerHTML;
|
||||
if (!selectorsToSync[value]) {
|
||||
selectorsToSync[value] = [];
|
||||
}
|
||||
selectorsToSync[value].push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function setupSelectorSync() {
|
||||
const selectorsToSync = findSelectorsToSyncByLanguage();
|
||||
Object.entries(selectorsToSync).forEach(([group, tabSetsByValue]) => {
|
||||
Object.entries(tabSetsByValue).forEach(([value, items]) => {
|
||||
items.forEach((item) => {
|
||||
item.addEventListener("click", (_event) => {
|
||||
setTabState(group, value);
|
||||
toggleAll(value, selectorsToSync[group]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return selectorsToSync;
|
||||
}
|
||||
|
||||
const selectorsToSync = setupSelectorSync();
|
||||
for (const [group, selectedName] of Object.entries(getTabSettings())) {
|
||||
const selectors = selectorsToSync[group];
|
||||
// it's possible that stale state gives us empty selections, so we explicitly check here.
|
||||
if (selectors) {
|
||||
toggleAll(selectedName, selectors);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function throttle(func, wait) {
|
||||
let waiting = false;
|
||||
return function () {
|
||||
if (!waiting) {
|
||||
func.apply(this, arguments);
|
||||
waiting = true;
|
||||
setTimeout(function () {
|
||||
waiting = false;
|
||||
}, wait);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1}
|
||||
@@ -23,13 +23,11 @@ book:
|
||||
repo-actions: [edit]
|
||||
downloads: [pdf, epub]
|
||||
sharing: [twitter, facebook]
|
||||
favicon: favicon.ico
|
||||
sidebar:
|
||||
logo: logo_white.png
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bibliography: references.bib
|
||||
|
||||
format:
|
||||
|
||||
@@ -95,6 +95,7 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni
|
||||
<meta name="quarto:offset" content="./">
|
||||
<link href="./RojavaRefineries.html" rel="next">
|
||||
<link href="./ch4.html" rel="prev">
|
||||
<link href="./favicon.ico" rel="icon">
|
||||
<script src="site_libs/quarto-html/quarto.js"></script>
|
||||
<script src="site_libs/quarto-html/popper.min.js"></script>
|
||||
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
|
||||
@@ -157,7 +158,7 @@ gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
|
||||
<nav id="quarto-sidebar" class="sidebar collapse sidebar-navigation floating overflow-auto">
|
||||
<div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
|
||||
<a href="./index.html" class="sidebar-logo-link">
|
||||
<img src="./logo_white.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
<img src="./logo_white_cropped.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
</a>
|
||||
<div class="sidebar-title mb-0 py-0">
|
||||
<a href="./">Google Earth Engine for OSINT</a>
|
||||
|
||||
@@ -32,6 +32,7 @@ ul.task-list li input[type="checkbox"] {
|
||||
<meta name="quarto:offset" content="./">
|
||||
<link href="./ch2.html" rel="next">
|
||||
<link href="./index.html" rel="prev">
|
||||
<link href="./favicon.ico" rel="icon">
|
||||
<script src="site_libs/quarto-html/quarto.js"></script>
|
||||
<script src="site_libs/quarto-html/popper.min.js"></script>
|
||||
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
|
||||
@@ -96,7 +97,7 @@ gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
|
||||
<nav id="quarto-sidebar" class="sidebar collapse sidebar-navigation floating overflow-auto">
|
||||
<div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
|
||||
<a href="./index.html" class="sidebar-logo-link">
|
||||
<img src="./logo_white.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
<img src="./logo_white_cropped.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
</a>
|
||||
<div class="sidebar-title mb-0 py-0">
|
||||
<a href="./">Google Earth Engine for OSINT</a>
|
||||
|
||||
@@ -32,6 +32,7 @@ ul.task-list li input[type="checkbox"] {
|
||||
<meta name="quarto:offset" content="./">
|
||||
<link href="./ch3.html" rel="next">
|
||||
<link href="./ch1.html" rel="prev">
|
||||
<link href="./favicon.ico" rel="icon">
|
||||
<script src="site_libs/quarto-html/quarto.js"></script>
|
||||
<script src="site_libs/quarto-html/popper.min.js"></script>
|
||||
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
|
||||
@@ -97,7 +98,7 @@ gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
|
||||
<nav id="quarto-sidebar" class="sidebar collapse sidebar-navigation floating overflow-auto">
|
||||
<div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
|
||||
<a href="./index.html" class="sidebar-logo-link">
|
||||
<img src="./logo_white.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
<img src="./logo_white_cropped.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
</a>
|
||||
<div class="sidebar-title mb-0 py-0">
|
||||
<a href="./">Google Earth Engine for OSINT</a>
|
||||
|
||||
@@ -32,6 +32,7 @@ ul.task-list li input[type="checkbox"] {
|
||||
<meta name="quarto:offset" content="./">
|
||||
<link href="./ch4.html" rel="next">
|
||||
<link href="./ch2.html" rel="prev">
|
||||
<link href="./favicon.ico" rel="icon">
|
||||
<script src="site_libs/quarto-html/quarto.js"></script>
|
||||
<script src="site_libs/quarto-html/popper.min.js"></script>
|
||||
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
|
||||
@@ -94,7 +95,7 @@ gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
|
||||
<nav id="quarto-sidebar" class="sidebar collapse sidebar-navigation floating overflow-auto">
|
||||
<div class="pt-lg-2 mt-2 text-left sidebar-header sidebar-header-stacked">
|
||||
<a href="./index.html" class="sidebar-logo-link">
|
||||
<img src="./logo_white.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
<img src="./logo_white_cropped.png" alt="" class="sidebar-logo py-0 d-lg-inline d-none">
|
||||
</a>
|
||||
<div class="sidebar-title mb-0 py-0">
|
||||
<a href="./">Google Earth Engine for OSINT</a>
|
||||
|
||||
@@ -32,6 +32,7 @@ ul.task-list li input[type="checkbox"] {
|
||||
<meta name="quarto:offset" content="./">
|
||||
<link href="./SyriaNTL.html" rel="next">
|
||||
<link href="./ch3.html" rel="prev">
|
||||
<link href="./favicon.ico" rel="icon">
|
||||
<script src="site_libs/quarto-html/quarto.js"></script>
|
||||
<script src="site_libs/quarto-html/popper.min.js"></script>
|
||||
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
|
||||
|
||||
BIN
docs/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -33,6 +33,7 @@ ul.task-list li input[type="checkbox"] {
|
||||
<script src="site_libs/quarto-search/quarto-search.js"></script>
|
||||
<meta name="quarto:offset" content="./">
|
||||
<link href="./ch1.html" rel="next">
|
||||
<link href="./favicon.ico" rel="icon">
|
||||
<script src="site_libs/quarto-html/quarto.js"></script>
|
||||
<script src="site_libs/quarto-html/popper.min.js"></script>
|
||||
<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
|
||||
@@ -249,7 +250,7 @@ gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
|
||||
<section id="introduction" class="level1 unnumbered">
|
||||
<h1 class="unnumbered">Introduction</h1>
|
||||
<p>The analysis of satellite imagery is a foundational element of open source investigations. In the past decade, the quantity, quality, and availability thereof has increased dramatically. Capabilities and insights that were once only available to governments are now accessible to the general public. Satellite imagery is being used to collect evidence of genocide and other war crimes in <a href="https://www.nbcnews.com/science/science-news/ukraine-satellites-war-crimes-rcna26291">Ukraine</a>, <a href="https://www.amnesty.org/en/latest/news/2016/04/nigeria-military-cover-up-of-mass-slaughter-at-zaria-exposed/">Nigeria</a>, <a href="https://www.amnesty.org/en/latest/news/2016/01/burundi-satellite-evidence-supports-witness-accounts-of-mass-graves/">Burundi</a>, <a href="https://www.amnesty.org/en/latest/news/2021/07/cameroon-satellite-images-reveal-devastation-in-anglophone-regions-2/">Cameroon</a>, <a href="https://www.aaas.org/resources/satellite-imagery-assessment-forced-relocations-near-luiswishi-mine">the DRC</a>, <a href="https://gsp.yale.edu/case-studies/sudan/maps-satellite-images/other-darfur-satellite-imagery">South Sudan</a>, <a href="https://gsp.yale.edu/resources/maps-satellite-images/papua">Papua</a>, and <a href="https://www.hrw.org/report/2016/04/04/unchecked-power/police-and-military-raids-low-income-and-immigrant-communities">Venezuela</a>. It has been used to <a href="https://www.theguardian.com/environment/2016/mar/02/new-satellite-mapping-a-game-changer-against-illegal-logging">monitor environmental degradation</a> and hold extractive industries to account from <a href="https://www.bellingcat.com/resources/2021/04/15/what-oil-satellite-technology-and-iraq-can-tell-us-about-pollution/">Iraq</a> to <a href="https://www.planet.com/pulse/the-observatory-of-extractive-industries-oie-shines-a-light-on-the-mining-industry-using-planets-satellite-data/">Guatemala</a>. The ability to analyze satellite imagery is a critical skill for anyone interested in open source investigations.</p>
|
||||
<p>Though no-code platforms such as Sentinelhub have been invaluable in allowing the OSINT community to access and process satellite imagery, the analytical capabilities of these platforms are limited. <a href="https://earthengine.google.com/#intro">Google Earth Engine (GEE)</a> is a cloud-based platform that stores petabytes of satellite imagery from a variety of sources and allows users to perform advanced analyses on Google servers for free using a browser-based interface. This textbook is designed for investigators who want to perform more sophisticated analysis using geospatial data, and assumes no prior knowledge of coding or remote sensing (satellite imagery analysis). It is organized into two parts– an introduction to remote sensing and GEE, and a series of case studies that demonstrate how to use GEE for open source investigations:</p>
|
||||
<p>Though no-code platforms such as Sentinelhub have been invaluable in allowing the OSINT community to access and process satellite imagery, the analytical capabilities of these platforms are limited. <a href="https://earthengine.google.com/#intro">Google Earth Engine (GEE)</a> is a cloud-based platform that stores petabytes of satellite imagery from a variety of sources and allows users to perform advanced analyses on Google servers for free using a browser-based interface. This textbook is designed for investigators who want to perform more sophisticated analysis using geospatial data, and assumes no prior knowledge of coding or remote sensing (satellite imagery analysis). It is organized into two parts: an introduction to remote sensing and GEE, and a series of case studies that demonstrate how to use GEE for open source investigations.</p>
|
||||
<section id="table-of-contents" class="level2">
|
||||
<h2 class="anchored" data-anchor-id="table-of-contents">Table of Contents</h2>
|
||||
<ul>
|
||||
@@ -271,9 +272,8 @@ gtag('config', 'G-RK9ZLZQ6GL', { 'anonymize_ip': true});
|
||||
<h2 class="anchored" data-anchor-id="what-is-google-earth-engine">What is Google Earth Engine?</h2>
|
||||
<p>As geospatial datasets—particularly satellite imagery collections—increase in size, researchers are increasingly relying on cloud computing platforms such as Google Earth Engine (GEE) to analyze vast quantities of data.</p>
|
||||
<p>GEE is free and allows users to write open-source code that can be run by others in one click, thereby yielding fully reproducible results. These features have put GEE on the cutting edge of scientific research. The following plot visualizes the number of journal articles conducted using different geospatial analysis software platforms:</p>
|
||||
<p><img src="../images/WoS Articles.png" class="img-fluid"></p>
|
||||
<p>Despite only being released in 2015, the number of geospatial journal articles using Google Earth Engine (shown in red above) has outpaced every other major geospatial analysis software, including ArcGIS, Python, and R in just five years. By storing and running computations on google servers, GEE is far more accessible to those who don’t have significant local computational resources; all you need is an internet connection.</p>
|
||||
<p>GEE applications have been developed and used to present interactive geospatial data visualizations by NGOs, Universities, the United Nations, and the European Commission.</p>
|
||||
<p><img src="./images/WoS Articles.png" class="img-fluid"></p>
|
||||
<p>Despite only being released in 2015, the number of geospatial journal articles using Google Earth Engine (shown in red above) has outpaced every other major geospatial analysis software, including ArcGIS, Python, and R in just five years. GEE applications have been developed and used to present interactive geospatial data visualizations by NGOs, Universities, the United Nations, and the European Commission. By storing and running computations on google servers, GEE is far more accessible to those who don’t have significant local computational resources; all you need is an internet connection.</p>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 8.1 KiB |
BIN
docs/logo_white_cropped.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
@@ -4,7 +4,7 @@
|
||||
"href": "index.html",
|
||||
"title": "Google Earth Engine for OSINT",
|
||||
"section": "",
|
||||
"text": "Introduction\nThe analysis of satellite imagery is a foundational element of open source investigations. In the past decade, the quantity, quality, and availability thereof has increased dramatically. Capabilities and insights that were once only available to governments are now accessible to the general public. Satellite imagery is being used to collect evidence of genocide and other war crimes in Ukraine, Nigeria, Burundi, Cameroon, the DRC, South Sudan, Papua, and Venezuela. It has been used to monitor environmental degradation and hold extractive industries to account from Iraq to Guatemala. The ability to analyze satellite imagery is a critical skill for anyone interested in open source investigations.\nThough no-code platforms such as Sentinelhub have been invaluable in allowing the OSINT community to access and process satellite imagery, the analytical capabilities of these platforms are limited. Google Earth Engine (GEE) is a cloud-based platform that stores petabytes of satellite imagery from a variety of sources and allows users to perform advanced analyses on Google servers for free using a browser-based interface. This textbook is designed for investigators who want to perform more sophisticated analysis using geospatial data, and assumes no prior knowledge of coding or remote sensing (satellite imagery analysis). It is organized into two parts– an introduction to remote sensing and GEE, and a series of case studies that demonstrate how to use GEE for open source investigations:"
|
||||
"text": "Introduction\nThe analysis of satellite imagery is a foundational element of open source investigations. In the past decade, the quantity, quality, and availability thereof has increased dramatically. Capabilities and insights that were once only available to governments are now accessible to the general public. Satellite imagery is being used to collect evidence of genocide and other war crimes in Ukraine, Nigeria, Burundi, Cameroon, the DRC, South Sudan, Papua, and Venezuela. It has been used to monitor environmental degradation and hold extractive industries to account from Iraq to Guatemala. The ability to analyze satellite imagery is a critical skill for anyone interested in open source investigations.\nThough no-code platforms such as Sentinelhub have been invaluable in allowing the OSINT community to access and process satellite imagery, the analytical capabilities of these platforms are limited. Google Earth Engine (GEE) is a cloud-based platform that stores petabytes of satellite imagery from a variety of sources and allows users to perform advanced analyses on Google servers for free using a browser-based interface. This textbook is designed for investigators who want to perform more sophisticated analysis using geospatial data, and assumes no prior knowledge of coding or remote sensing (satellite imagery analysis). It is organized into two parts: an introduction to remote sensing and GEE, and a series of case studies that demonstrate how to use GEE for open source investigations."
|
||||
},
|
||||
{
|
||||
"objectID": "index.html#table-of-contents",
|
||||
@@ -18,7 +18,7 @@
|
||||
"href": "index.html#what-is-google-earth-engine",
|
||||
"title": "Google Earth Engine for OSINT",
|
||||
"section": "What is Google Earth Engine?",
|
||||
"text": "What is Google Earth Engine?\nAs geospatial datasets—particularly satellite imagery collections—increase in size, researchers are increasingly relying on cloud computing platforms such as Google Earth Engine (GEE) to analyze vast quantities of data.\nGEE is free and allows users to write open-source code that can be run by others in one click, thereby yielding fully reproducible results. These features have put GEE on the cutting edge of scientific research. The following plot visualizes the number of journal articles conducted using different geospatial analysis software platforms:\n\nDespite only being released in 2015, the number of geospatial journal articles using Google Earth Engine (shown in red above) has outpaced every other major geospatial analysis software, including ArcGIS, Python, and R in just five years. By storing and running computations on google servers, GEE is far more accessible to those who don’t have significant local computational resources; all you need is an internet connection.\nGEE applications have been developed and used to present interactive geospatial data visualizations by NGOs, Universities, the United Nations, and the European Commission."
|
||||
"text": "What is Google Earth Engine?\nAs geospatial datasets—particularly satellite imagery collections—increase in size, researchers are increasingly relying on cloud computing platforms such as Google Earth Engine (GEE) to analyze vast quantities of data.\nGEE is free and allows users to write open-source code that can be run by others in one click, thereby yielding fully reproducible results. These features have put GEE on the cutting edge of scientific research. The following plot visualizes the number of journal articles conducted using different geospatial analysis software platforms:\n\nDespite only being released in 2015, the number of geospatial journal articles using Google Earth Engine (shown in red above) has outpaced every other major geospatial analysis software, including ArcGIS, Python, and R in just five years. GEE applications have been developed and used to present interactive geospatial data visualizations by NGOs, Universities, the United Nations, and the European Commission. By storing and running computations on google servers, GEE is far more accessible to those who don’t have significant local computational resources; all you need is an internet connection."
|
||||
},
|
||||
{
|
||||
"objectID": "ch3.html#getting-started",
|
||||
@@ -82,5 +82,96 @@
|
||||
"title": "Refinery Detection",
|
||||
"section": "3. Validation",
|
||||
"text": "3. Validation\nThe image above should look somewhat familiar. It’s Ger Zero, where we trained part of our model. We can see in red the areas which the model predicts to be oil pollution. These largley align with the areas that we can see as being contaminated based on the high resolution basemap. It’s not perfect, but it’s pretty good.\nLet’s scroll to another area, far from where the model was trained. This image shows two clusters of makeshift refineries which were identified by the model. This is good, though we can only get so far by visually inspecting the output from our model. To get a better sense of our model’s performance, we can use the validation data that we generated previously. Remember, these are labeled points which our model was not trained on, and has never seen before.\nWe’ll take the validation featureCollection containing our labeled points, and have our model classify it.\nvar validated = validation.classify(model);\nNow the validated variable is a featureCollection which contains both manual labels and predicted labels from our model. We can compare the manual labels to the predicted output to get a sense of how well our model is performing. This is called a Confusion Matrix (or an Error Matrix):\nvar testAccuracy = validated.errorMatrix('class', 'classification');\n\nprint('Confusion Matrix ', testAccuracy);\n\n\n\n\n\n\n\n\n\n\n\n\n\nLabels\n\n\n\n\n\n\n\nOil\nUrban\nAgriculture\n\n\n\nOil\n876\n1\n5\n\n\nPrediction\nUrban\n0\n168\n8\n\n\n\nAgriculture\n1\n4\n514\n\n\n\nNow, we can see that of the 877 points that were labeled “oil”, only one was falsely predicted to be agicultural land. The model also falsely predicted as oil one point that was labeled urban, and five points that were labeled agriculture. Not bad. We can get a sense of the model’s overall accuracy using the accuracy function on the confusion matrix:\nprint('Validation overall accuracy: ', testAccuracy.accuracy())\nThis tells us that the overall accuracy of our model is around 98%. However, we shouldn’t take this estimate at face value. There are a number of complicated reasons (spatial autocorrelation in the training data, for example) why this figure is probably inflatred. If we were submitting this analysis to a peer-reviewed journal, we’d take great care in addressing this, but for our purposes we can use the accuracy statistics to guide our analysis and get a rough sense of how well the model is performing.\nThis model isn’t perfect; it often misclassifies the shorelines of lakes as oil, or certain parts of urban areas. As previously mentioned, training a model is often an iterative process. At this stage, if your accuracy is not as high as you’d like it to be, you can use the output to figure out how to tweak the model. For example, you may observe that your model is confusing urban areas with oil spills. You can draw a polygon over the erroneous area, label it urban landcover and retrain the model thereby hopefully improving accuracy. We could further refine our model in this way."
|
||||
},
|
||||
{
|
||||
"objectID": "ch1.html#active-and-passive-sensors",
|
||||
"href": "ch1.html#active-and-passive-sensors",
|
||||
"title": "1 Remote Sensing",
|
||||
"section": "1.1 Active and Passive Sensors",
|
||||
"text": "1.1 Active and Passive Sensors\nRemote sensing is the science of obtaining information about an object or phenomenon without making physical contact with the object. Remote sensing can be done with various types of electromagnetic radiation such as visible, infrared, or microwave. The electromagnetic radiation is either emitted or reflected from the object being sensed. The reflected radiation is then collected by a sensor and processed to obtain information about the object.\n\nWhile most satellite imagery is optical, meaning it captures sunlight reflected by the earth’s surface, Synthetic Aperture Radar (SAR) satellites such as Sentinel-1 work by emitting pulses of radio waves and measuring how much of the signal is reflected back. This is similar to the way a bat uses sonar to “see” in the dark: by emitting calls and listening to echoes."
|
||||
},
|
||||
{
|
||||
"objectID": "ch1.html#resolution",
|
||||
"href": "ch1.html#resolution",
|
||||
"title": "1 Remote Sensing",
|
||||
"section": "1.2 Resolution",
|
||||
"text": "1.2 Resolution\nResolution is one of the most important attributes of satellite imagery.\nhere are three types of resolution: spatial, spectral, and temporal.\n\n1.2.1 Spatial Resolution\nSpatial resolution governs how “sharp” an image looks. The Google Maps satellite basemap, for example, is really sharp Most of the optical imagery that is freely available has relatively low spatial resolution (it looks more grainy than, for example, the Google satellite basemap),\n \n\n\n1.2.2 Spectral Resolution\nWhat open source imagery lacks in spatial resolution it often makes up for with spectral resolution. Really sharp imagery from MAXAR, for example, collects\nDifferent materials reflect light differently. An apple absorbs shorter wavelengths (e.g. blue and green), and reflects longer wavelengths (red). Our eyes use that information– the color– to distinguish between different objects. But our eyes can only see a relatively small sliver of the electromagnetic spectrum covering blue, yellow, and red; we can’t see UV or infrared wavelengths, for example, though the extent to which different materials reflect or absorb these wavelengths is just as useful for distinguishing between them. For example, Astroturf (fake plastic grass) and real grass will both look green to us, espeically from a satellite image. But living plants absorb radiation from the sun in a part of the light spectrum that we can’t see. There’s a spectral index called the Normalized Difference Vegetation Index (NDVI) which exploits this fact to isolate vegetation in multispectral satellite imagery. So if we look at Gilette Stadium near Boston, we can tell that the three training fields south of the stadium are real grass (they generate high NDVI values, showing up red), while the pitch in the stadium itself is astroturf (generating low NDVI values, showing up blue).\n\n\n\nVHR image of Gilette Stadium with Sentinel-2 derived NDVI overlay\n\n\nIn other words, even though these fields are all green and indistinguishable to the human eye, their spectral profiles beyond the visible light spectrum differ, and we can use this information to distinguish between them. Below is a plot of the spectral profiles of different materials, including oil.\n\n\n\nThe European Space Agency’s Sentinel-2 satellite collects spectral information well beyond the visible light spectrum, enabling this sort of analysis. It chops the electromagnetic spectrum up into “bands”, and measures how strongly wavelengths in each of those bands is reflected:\n\nWe’ll be using this satellite to distinguish between oil and other materials, similar to the way we were able to distinguish between real and fake grass at Gilette Stadium. First, we’ll have to do a bit of pre-processing on the Sentinel-2 imagery after which we’ll train a machine learning model to identify oil.\n\n\n1.2.3 Temporal Resolution\nFinally, the frequency with which we There is often a tradeoff between spatial and temporal resolution.\nThe Google Maps basemap is very high resolution, available globally, and is freely available. But it has no temporal dimension: it’s a snapshot from one particular point in time. If the thing we’re interested in involves changes over time, this basemap will be of limited use.\nThe “revisit rate” is the amount of time it takes for the satellite to pass over the same location twice. The revisit rate is inversely proportional to the satellite’s altitude: the higher the satellite is, the more frequently it can pass over the same location. This generally means that there’s a tradeoff between spatial resolution and temporal resolution: the higher the spatial resolution, the lower the revisit rate. However, some satellite constellations such as Planet’s SkySat are able to achieve both high spatial and temporal resolution by launching lots of small satellites into orbit at once. Below is a comparison of revisit rates for various satellites:\n\nSentinel 1: 3 days (6 days as of 23/12/21, since Sentinel-1B was decomisioned)\nSentinel 2: 5 days\nLandsat 8-9: 8 days\nPlanet SkySat: 2-3 hours"
|
||||
},
|
||||
{
|
||||
"objectID": "ch1.html#orbits",
|
||||
"href": "ch1.html#orbits",
|
||||
"title": "1 Remote Sensing",
|
||||
"section": "1.3 Orbits",
|
||||
"text": "1.3 Orbits\nThe Landsat satellites are in a sun-synchronous orbit, meaning they pass over the same spot on Earth at the same time every day. The Sentinel satellites are in a polar orbit, meaning they pass over the same spot on Earth twice a day, once in the morning and once in the afternoon. NASA have created a great visualisation showing the orbits of the Landsat and Sentinel-2 satellites:\n\nThe Sentinel satellites are in a lower orbit than Landsat, meaning they are closer to the Earth and have a higher resolution."
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#optical-imagery",
|
||||
"href": "ch2.html#optical-imagery",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.1 Optical Imagery",
|
||||
"text": "2.1 Optical Imagery\n\n\n\nSentinel-2 timelapse showing the ancient city of Hasankeyf being flooded following the construction of a dam by the Turkish government.\n\n\nOptical satellite imagery is the bread and butter of many open source investiagtions. It would be tough to list off all of the possible use cases, so here’s a handy flowchart:\n\n\n\n\n%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#FFFFFF' ,'primaryBorderColor':'#000000' , 'lineColor':'#009933'}}}%%\n\nflowchart\n A(Does it happen outside?) \n A--> B(Yes)\n A--> C(No)\n D(Is it very small?)\n B-->D\n E(Yes)\n F(No)\n D-->F\n D-->E\nG(Use optical satellite imagery)\nH(Don't use optical satellite imagery)\nE-->H\nF-->G\nC-->H\n\n\n\n\n\n\n\n\nThis is, of course, a bit of an exaggeration. But if you’re interested in a visible phenomenon that happens outdoors and that isn’t very tiny, chances are an earth-observing satellite has taken a picture of it. What that picture can tell you naturally depends on what you’re interested in learning. For a deeper dive into analyzing optical satellite imagery, see the subsection on multispectral remote sensing..\nThere are several different types of optical satellite imagery available in the GEE catalogue. The main collections are the Landsat and Sentinel series of satellites, which are operated by NASA and the European Space Agency, respectively. Landsat satellites have been in orbit since 1972, and Sentinel satellites have been in orbit since 2015. Norway’s International Climate and Forest Initiative (NICFI) has also contributed to the GEE catalogue by providing a collection of optical imagery from Planet’s PlanetScope satellites. These are higher resolution (4.7 meters per pixel) than Landsat (30m/px) and Sentinel-2 (10m/px), but are only available for the tropics. Even higher resolution imagery (60cm/px) is available from the GEE catalogue from the National Agriculture Imagery Program, but it is only available for the United States. For more details, see the “Datasets” section below.\n\nApplications\n\nGeolocating pictures\n\nSome of Bellingcat’s earliest work involved figuring out where a picture was taken by cross-referencing it with optical satellite imagery.\n\nGeneral surveillance\n\nMonitoring Chinese missile silo construction.\nAmassing evidence of genocide in Bucha, Ukraine\n\nDamage detection\n\nUkraine\nMali\nAround the World\n\nVerifying the locations of artillery/missile/drone strikes\n\nThe 2019 attack on Saudi Arabia’s Abqaiq oil processing facility.\n\nMonitoring illegal mining/logging\n\nGlobal Witness investigation into illegal mining by militias in Myanmar.\nTracking illegal logging across the world.\n\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\nLandsat 1-5\n1972–1999\n30m\nGlobal\n\n\nLandsat 7\n1999–2021\n30m\nGlobal\n\n\nLandsat 8\n2013–Present\n30m\nGlobal\n\n\nLandsat 9\n2021–Present\n30m\nGlobal\n\n\nSentinel-2\n2015–Present\n10m\nGlobal\n\n\nNICFI\n2015-Present\n4.7m\nTropics\n\n\nNAIP\n2002-2021\n0.6m\nUSA"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#radar-imagery",
|
||||
"href": "ch2.html#radar-imagery",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.2 Radar Imagery",
|
||||
"text": "2.2 Radar Imagery\n\n\n\nShips and interference from a radar system are visible in Zhuanghe Wan, near North Korea.\n\n\nSynthetic Aperture Radar imagery (SAR) is a type of remote sensing that uses radio waves to detect objects on the ground. SAR imagery is useful for detecting objects that are small, or that are obscured by clouds or other weather phenomena. SAR imagery is also useful for detecting objects that are moving, such as ships or cars.\n\nApplications\n\nChange/Damage detection\nTracking military radar systems\nMaritime surveillance\nMonitoring illegal mining/logging\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\nSentinel 1\n2014-Present\n10m\nGlobal"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#nighttime-lights",
|
||||
"href": "ch2.html#nighttime-lights",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.3 Nighttime Lights",
|
||||
"text": "2.3 Nighttime Lights\n\n\n\nA timelapse of nighttime lights over Northern Iraq showing the capture and liberation of Mosul by ISIS.\n\n\nSatellite images of the Earth at night a useful proxy for human activity. The brightness of a given area at night is a function of the number of people living there and the nature of their activities. The effects of conflict, natural disasters, and economic development can all be inferred from changes in nighttime lights.\nThe timelapse above reveals a number of interesting things: The capture of Mosul by ISIS in 2014 and the destruction of its infrastructure during the fighting (shown as the city darkening), as well as the liberation of the city by the Iraqi military in 2017 are all visible in nighttime lights. The code to create this gif, as well as a more in-depth tutorial on the uses of nighttime lights, can be found in the “War at Night” case study.\n\nApplications\n\nDamage detection\nIdentifying gas flaring/oil production\nIdentifying urban areas/military bases illuminated at night\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\nDMSP-OLS\n1992-2014\n927m\nGlobal\n\n\nVIIRS\n2014-Present\n463m\nGlobal"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#climate-and-atmospheric-data",
|
||||
"href": "ch2.html#climate-and-atmospheric-data",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.4 Climate and Atmospheric Data",
|
||||
"text": "2.4 Climate and Atmospheric Data\n\n\n\nSulphur Dioxide plume resulting from ISIS attack on the Al-Mishraq Sulphur Plant in Iraq\n\n\nClimate and atmospheric data can be used to track the effects of conflict on the environment. The European Space Agency’s Sentinel-5p satellites measure the concentration of a number of atmospheric gases, including nitrogen dioxide, methane, and ozone. Measurements are available on a daily basis at a fairly high resolution (1km), allowing for the detection of localized sources of pollution such as oil refineries or power plants. For example, see this Bellingcat article in which Wim Zwijnenburg and I trace pollution to specific facilities operated by multinational oil companies in Iraq.\nThe Copernicus Atmosphere Monitoring Service (CAMS) provides similar data at a lower spatial resolution (45km), but measurements are avaialble on an hourly basis. The timelapse above utilizes CAMS data to show a sulphur dioxide plume resulting from an ISIS attack on the Al-Mishraq Sulphur Plant in Iraq. The plant was used to produce sulphuric acid, for use in fertilizers and pesticides. The attack destroyed the plant, causing a fire which burned for a month and released 21 kilotons of sulphur dioxide into the atmosphere per day; the largest human-made release of sulphur dioxide in history.\n\nApplications\n\nMonitoring of airborne pollution\nTracing pollution back to specific facilities and companies\nVisualizing the effects of one-off environmental catastrophes\n\nNordstream 1 leak\nISIS setting Mishraq sulphur plant on fire\n\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\nCAMS NRT\n2016-Present\n44528m\nGlobal\n\n\nSentinel-5p\n2018-Present\n1113m\nGlobal"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#mineral-deposits",
|
||||
"href": "ch2.html#mineral-deposits",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.5 Mineral Deposits",
|
||||
"text": "2.5 Mineral Deposits\n\n\n\nZinc deposits across Central Africa\n\n\nMining activities often play an important role in conflict. According to an influential study, “the historical rise in mineral prices might explain up to one-fourth of the average level of violence across African countries” between 1997 and 2010. Data on the location of mineral deposits can be used to identify areas where mining activities are likely to be taking place, and several such datasets are available in Google Earth Engine.\n\nApplications\n\nMonitoring mining activity\nIdentifying areas where mining activities are likely to be taking place\nMapping the distribution of resources in rebel held areas in conflicts fueled by resource extraction\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\niSDA\n2001-2017\n30m\nAfrica"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#fires",
|
||||
"href": "ch2.html#fires",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.6 Fires",
|
||||
"text": "2.6 Fires\n\n\n\nDetected fires over Ukraine since 27/02/2022 showing the frontline of the war\n\n\nEarth-observing satellites can detect “thermal anomalies” (fires) from space. NASA’s Fire Information for Resource Management System (FIRMS) provides daily data on active fires in near real time, going back to the year 2000. Carlos Gonzales wrote a comprehensive Bellingcat article on the use of FIRMS to monitor war zones from Ukraine to Ethiopia. The map above shows that FIRMS detected fires over Eastern Ukraine trace the frontline of the war.\nFIRMS data are derived from the MODIS satellite, but only show the central location and intensity of a detected fire. Another MODIS product (linked in the table below) generates a monthly map of burned areas, which can be used to assess the spatial extent of fires.\n\nApplications\n\nIdentification of possible artillery strikes/fighting in places like Ukraine\nEnvironmental warfare and “scorched earth” policies\nLarge scale arson\n\ne.g. Refugee camps burned down in Myanmar\n\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\nFIRMS\n2000-Present\n1000m\nGlobal\n\n\nMODIS Burned Area\n2000-Present\n500m\nGlobal"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#population-density-estimates",
|
||||
"href": "ch2.html#population-density-estimates",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.7 Population Density Estimates",
|
||||
"text": "2.7 Population Density Estimates\n\n\n\nPopulation density estimates around Pyongyang, North Korea\n\n\nSometimes, we may want to get an estimate the population in a specific area to ballpark how many people might be affected by a natural disaster, a counteroffensive, or a missile strike. You can’t really google “what is the population in this rectangle i’ve drawn in Northeastern Syria?” and get a good answer. Luckily, there are several spatial population datasets hosted in GEE that let you do just that. Some, such as WorldPop, provide estimated breakdowns by age and sex as well. However, it is extremely important to bear in mind that these are estimates, and will not take into account things like conflict-induced displacement. For example, Oak Ridge National Laboratory’s LandScan program has released high-resolution population data for Ukraine, but this pertains to the pre-war population distribution. The war has radically changed this distribution, so these estimates no longer reflect where people are. Still, this dataset could be used to roughly estimate displacement or the number of people who will need new housing.\n\nApplications:\n\nRough estimates of civilians at risk from conflict or disaster, provided at a high spatial resolution\n\n\n\nDatasets\n\n\n\nSensor\nTimeframe\nResolution\nCoverage\n\n\n\n\nWorldpop\n2000-2021\n92m\nGlobal\n\n\nGPW\n2000-2021\n927m\nGlobal\n\n\nLandScan\n2013–Present\n100m\nUkraine"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#building-footprints",
|
||||
"href": "ch2.html#building-footprints",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.8 Building Footprints",
|
||||
"text": "2.8 Building Footprints\n\n\n\nBuilding footprints in Mariupol, Ukraine colored by whether the building is damaged\n\n\nA building footprint dataset contains the two dimensional outlines of buildings in a given area. Currently, GEE hosts one building footprint dataset which covers all of Africa. In 2022, Microsoft released a free global building footprint dataset, though to use it in Earth Engine you’ll have to download it from their GitHub page and upload it manually to GEE. The same goes for OpenStreetMap (OSM), a public database of building footprints, roads, and other features that also contains useful annotations for many buildings indicating their use. Benjamin Strick has a great youtube video on conducting investigations using OSM data.\n\nApplications:\n\nJoining damage estimate data with the number of buildings in an area\n\n\n\nDatasets\n\n\n\nDataset\nTimeframe\nCoverage\n\n\n\n\nOpen Buildings\n2022\nAfrica"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#administrative-boundaries",
|
||||
"href": "ch2.html#administrative-boundaries",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.9 Administrative Boundaries",
|
||||
"text": "2.9 Administrative Boundaries\n\n\n\nSecond-level administrative boundaries in Yemen\n\n\nSpatial analysis often have to aggregate information over a defined area; we may want to assess the total burned area by province in Ukraine, or count the number of Saudi airstrikes by district in Yemen. For that, we need data on these administrative boundaries. GEE hosts several such datasets at the country, province, and district (or equivalent) level.\n\nApplications\n\nQuick spatial calculations for different provinces/districts in a country\n\ne.g. counts of conflict events by district over time\n\n\n\n\nDatasets\n\n\n\nDataset\nTimeframe\nCoverage\n\n\n\n\nFAO GAUL\n2015\nGlobal"
|
||||
},
|
||||
{
|
||||
"objectID": "ch2.html#global-power-plant-database",
|
||||
"href": "ch2.html#global-power-plant-database",
|
||||
"title": "2 Data Acquisition",
|
||||
"section": "2.10 Global Power Plant Database",
|
||||
"text": "2.10 Global Power Plant Database\n\n\n\nPower plants in Ukraine colored by type\n\n\nThe Global Power Plant Database is a comprehensive, open source database of power plants around the world. It centralizes power plant data to make it easier to navigate, compare and draw insights. Each power plant is geolocated and entries contain information on plant capacity, generation, ownership, and fuel type. As of June 2018, the database includes around 28,500 power plants from 164 countries. The database is curated by the World Resources Institude (WRI).\n\nApplications:\n\nAnalyzing the impact of conflict on critical infrastructure.\n\ne.g. fighting in Ukraine taking place around nuclear power facilities.\n\nCould be combined with the atmospheric measurements of different pollutants and the population estimates data to assess the impact of various forms of energy generation on air quality and public health.\n\n\n\nDatasets\n\n\n\nDataset\nTimeframe\nCoverage\n\n\n\n\nGPPD\n2018\nGlobal"
|
||||
}
|
||||
]
|
||||
@@ -20,8 +20,6 @@ As geospatial datasets—particularly satellite imagery collections—increase i
|
||||
|
||||
GEE is free and allows users to write open-source code that can be run by others in one click, thereby yielding fully reproducible results. These features have put GEE on the cutting edge of scientific research. The following plot visualizes the number of journal articles conducted using different geospatial analysis software platforms:
|
||||
|
||||

|
||||

|
||||
|
||||
Despite only being released in 2015, the number of geospatial journal articles using Google Earth Engine (shown in red above) has outpaced every other major geospatial analysis software, including ArcGIS, Python, and R in just five years. By storing and running computations on google servers, GEE is far more accessible to those who don’t have significant local computational resources; all you need is an internet connection.
|
||||
|
||||
GEE applications have been developed and used to present interactive geospatial data visualizations by NGOs, Universities, the United Nations, and the European Commission.
|
||||
Despite only being released in 2015, the number of geospatial journal articles using Google Earth Engine (shown in red above) has outpaced every other major geospatial analysis software, including ArcGIS, Python, and R in just five years. GEE applications have been developed and used to present interactive geospatial data visualizations by NGOs, Universities, the United Nations, and the European Commission. By storing and running computations on google servers, GEE is far more accessible to those who don’t have significant local computational resources; all you need is an internet connection.
|
||||
BIN
logo_white copy.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
logo_white.png
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 8.1 KiB |