diff --git a/src/visualizations/custom/maps/map_minimal.js b/src/visualizations/custom/maps/map_minimal.js deleted file mode 100644 index ce6df7f..0000000 --- a/src/visualizations/custom/maps/map_minimal.js +++ /dev/null @@ -1,650 +0,0 @@ -import mapboxgl from 'mapbox-gl'; -import stateData from './mapbox-boundaries-adm1-v3_4.json'; -import cbsaData from './mapbox-boundaries-sta2-v3_4.json'; -import zipData from './mapbox-boundaries-pos4-v3_4.json'; -import regeneratorRuntime from "regenerator-runtime"; -import "core-js/stable"; - -if (mapboxgl.version.indexOf('2.9.') === 0) Object.defineProperty(window, 'caches', { value: undefined }); - -function numberWithCommas(x) { - if(!x) { - return undefined; - } - x = x.toFixed(0); - var parts = x.toString().split("."); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); - return parts.join("."); -}; - -looker.plugins.visualizations.add({ - id: "thrive_custom_special_granularity_map", - label: "Custom Layered Mapbox Map", - // Set up the initial state of the visualization - create: function (element, config) { - - // Insert a `; - - // Create the top layer selector bar - //###################################################################################// - - this.__mapStatesButton = document.createElement('button'); - this.__mapStatesButton.innerHTML = "States"; - this.__mapStatesButton.className = "map-paginator active"; - this.__mapStatesButton.id = "states-join"; - - this.__mapCBSAsButton = document.createElement('button'); - this.__mapCBSAsButton.innerHTML = "CBSAs"; - this.__mapCBSAsButton.className = "map-paginator"; - this.__mapCBSAsButton.id = "cbsas-join"; - - this.__mapZipCodesButton = document.createElement('button'); - this.__mapZipCodesButton.innerHTML = "Zip Codes"; - this.__mapZipCodesButton.className = "map-paginator"; - this.__mapZipCodesButton.id = "zips-join"; - - this.__mapBEsButton = document.createElement('button'); - this.__mapBEsButton.innerHTML = "Business Entities"; - this.__mapBEsButton.className = "map-paginator"; - this.__mapBEsButton.id = "business-entities-join"; - - this.__mapPaginatorWrapper = document.createElement('div'); - this.__mapPaginatorWrapper.className = "map-paginator__wrapper"; - - this.__mapPaginatorWrapper.appendChild(this.__mapStatesButton); - this.__mapPaginatorWrapper.appendChild(this.__mapCBSAsButton); - this.__mapPaginatorWrapper.appendChild(this.__mapZipCodesButton); - this.__mapPaginatorWrapper.appendChild(this.__mapBEsButton); - - element.appendChild(this.__mapPaginatorWrapper); - - this.__selectedLocalesWrapper = document.createElement('div'); - this.__selectedLocalesWrapper.className = "selected-locales__wrapper"; - element.appendChild(this.__selectedLocalesWrapper); - - //###################################################################################// - - this.__mapBox = document.createElement('div'); - this.__mapBox.style.height = '100%'; - this.__mapBox.style.width = '100%'; - this.__mapBox.id = "map"; - - element.appendChild(this.__mapBox); - - mapboxgl.accessToken = 'pk.eyJ1IjoiZHVuY2FuY2ZyYXNlciIsImEiOiJjbDRvbDlmZWQwMGdzM2ZxazZybTVkdDQ0In0.xL5_LBkos5tYRbLxR0tQRQ'; - this.__map = new mapboxgl.Map({ - container: 'map', - style: 'mapbox://styles/mapbox/light-v10', - center: [-98.5795, 39.8283], - zoom: 4, - projection: 'globe' - }); - - this.__map.addControl(new mapboxgl.NavigationControl()); - this.__map.addControl(new mapboxgl.FullscreenControl()); - - var link = document.createElement('link'); - link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css'; - link.rel = 'stylesheet'; - - var script = document.createElement('script'); - script.src = 'https://kit.fontawesome.com/f2060bf509.js'; - script.crossOrigin = "anonymous"; - - document.head.appendChild(link); - document.head.appendChild(script); - }, - // Render in response to the data or settings changing - updateAsync: function (data, element, config, queryResponse, details, done) { - - // Reload Handler to accomodate the on load event only running once - console.log("Running Entire Visualization Update") - - function throwMessage(locales) { - console.log("A1") - window.parent.parent.postMessage({ message: "crossFilterLocale", value: locales }, "*") - } - - function changeGranularity(g) { - console.log("A2") - window.parent.parent.postMessage({ message: "changeGranularity", value: g }, "*") - } - - let mapgl = this.__map - - const measureName = queryResponse.fields.measures[0].name; - const measureLabel = queryResponse.fields.measures[0].label_short; - const localesWrapper = this.__selectedLocalesWrapper - - let hoveredStateId = null; // Tracks hovered state and updates with popup - let filteredStateNames = [] - - async function runSelectionUpdate(element) { - console.log("A3") - const prevParent = document.getElementById("selectedLocaleContainer"); - if(prevParent) { - console.log("B3") - element.removeChild(prevParent) - }; - - const parent = document.createElement("div"); - parent.id = "selectedLocaleContainer"; - - const values = (Object.values(filteredStateNames)).map(element => element[0]) - console.log("values", values) - - for(let i = 0; i < values.length; i++) { - if(!values[i]) { - continue; - } - if(i > 1) { - const moreWrapper = document.createElement("div") - moreWrapper.className = "selected-locale__more" - const moreText = document.createElement("span") - moreText.innerHTML = `+${values.length - 2} more locales`; - moreWrapper.appendChild(moreText) - parent.appendChild(moreWrapper) - break; - } - const selectedLocale = document.createElement("div") - const selectedLocaleText = document.createElement("span") - const removeButton = document.createElement('div') - removeButton.innerHTML = ''; - - removeButton.addEventListener("click", () => { - values.splice(i, 1) - runSelectionUpdate(element) - }) - - selectedLocaleText.innerHTML = values[i] - selectedLocale.className = "selected-locale" - - selectedLocale.appendChild(selectedLocaleText) - selectedLocale.appendChild(removeButton) - parent.appendChild(selectedLocale) - } - - throwMessage(values) - - element.appendChild(parent) - }; - - mapgl.on('load', () => { - console.log("A4") - createStatesViz(); - }); - - mapgl.on('idle', () => { - console.log("A5") - this.__mapStatesButton.addEventListener("click", changeActive); - this.__mapCBSAsButton.addEventListener("click", changeActive); - this.__mapZipCodesButton.addEventListener("click", changeActive); - this.__mapBEsButton.addEventListener("click", changeActive); - }) - - const changeActive = (e) => { - console.log("A6") - /* For reference, e.target is one of the buttons on top of the map. - The event listeners are added in a map.on('idle', () => {}) but have - previously been added in map.on('load', () => {}) to the same effect*/ - - //e.preventDefault(); - //e.stopPropagation(); - - if(!e.target.classList.contains("active")) { - console.log("B6") - // Updates the nav-bar active status - const els = document.getElementsByClassName("map-paginator"); - for (let i = 0; i < els.length; i++) { - if(els[i].classList.contains("active")) { - els[i].classList.remove("active"); - }; - }; - - e.target.classList.add("active"); - - // Updates the layers' active status - for (let j = 0; j < this.__LAYERNAMES.length; j++) { - if(this.__LAYERNAMES[j].name !== e.target.id) { - mapgl.setLayoutProperty(this.__LAYERNAMES[j].name, 'visibility', 'none'); - }; - }; - - const filteredDown = this.__LAYERNAMES.filter(item => item.name === e.target.id) - console.log(e.target.id) - - if(filteredDown.length > 0) { - console.log("B6A") - mapgl.setLayoutProperty(e.target.id, 'visibility', 'visible'); - changeGranularity(filteredDown[0].groupingName) - } - - console.log("DEBUG: GETTING LAYER VISIBILITY") - console.log(mapgl.getStyle().layers) - } - } - - function getMaxState(arr) { - console.log("A7") - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.state"].value)); - try { - console.log("B7") - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function createStatesViz() { - console.log("A8") - const lookupData = filterLookupTable(); - - function filterLookupTable(lookupTable) { - const lookupData = {}; - - const searchData = stateData.adm1.data.all - - Object.keys(searchData).forEach(function(key) { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['name']] = featureData - } - }) - return lookupData; - } - - mapgl.addSource('statesData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-adm1-v3' - }); - - const maxValue = getMaxState(lookupData) - - mapgl.addLayer({ - id: 'states-join', - type: 'fill', - source: 'statesData', - /*'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - },*/ - 'source-layer': 'boundaries_admin_1', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - - mapgl.on('mousemove', 'states-join', (e) => { - console.log("A9") - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const state = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove; - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'states-join', () => { - console.log("A10") - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: false } - ); - popup.remove() - } - hoveredStateId = null; - }); - - mapgl.on('click', (e) => { - console.log("A11") - // Set `bbox` as 5px reactangle area around clicked point. - const bbox = [ - [e.point.x, e.point.y], - [e.point.x, e.point.y] - ]; - - const selectedFeatures = mapgl.queryRenderedFeatures(bbox, { - layers: ['states-join'] - }); - - const name = selectedFeatures.map( - (feature) => feature.state.name - ); - - const selectedFeatureName = name[0] - - if(!lookupData.hasOwnProperty(selectedFeatureName) || filteredStateNames[activeLayer].includes(selectedFeatureName)) { - return; - } - - - filteredStateNames[activeLayer].push(selectedFeatureName) - console.dir(filteredStateNames) - runSelectionUpdate(localesWrapper); - }); - - function setStates() { - console.log("A12") - console.log("%c Setting States for States", 'color: #009900') - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.state"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "statesData", - sourceLayer: 'boundaries_admin_1', - id: lookupData[row["dim_zi_map_vis.state"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - fipsCode: lookupData[row["dim_zi_map_vis.state"].value].unit_code, - name: lookupData[row["dim_zi_map_vis.state"].value].name, - hovered: false - } - ) - } - } - - function createLegend() { - console.log("A13") - try { - const oldLegendBox = document.getElementById("mapboxLegend") - oldLegendBox.parentNode.removeChild(oldLegendBox) - } catch { - console.log("Unable to remove old legend because there is no old legend.") - } - - const legendBox = document.createElement("div") - const legendLeft = document.createElement("div") - const legendRight = document.createElement("div") - const legendBar = document.createElement("div") - const legendRightTop = document.createElement("div") - const legendRightBottom = document.createElement("div") - - legendBox.className = "legend-box" - legendLeft.className = "legend-left" - legendRight.className = "legend-right" - legendBar.className = "legend-bar" - - legendRightTop.innerHTML = numberWithCommas(maxValue) - legendRightBottom.innerHTML = 1 - - legendLeft.appendChild(legendBar) - - legendRight.appendChild(legendRightTop) - legendRight.appendChild(legendRightBottom) - - legendBox.appendChild(legendLeft) - legendBox.appendChild(legendRight) - - legendBox.id = "mapboxLegend" - - element.appendChild(legendBox) - } - - createLegend() - - // Check if `statesData` source is loaded. - function setAfterLoadStates(event) { - console.log("A14") - console.log("sourceEvent", event) - if (event.sourceID !== 'statesData' && !event.isSourceLoaded) return; - setStates(); - console.log("%c Turning off Sourcedata event listener for states", 'color: #ff0000') - mapgl.off('sourcedata', setAfterLoadStates); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('statesData')) { - console.log("A15") - console.log("%c States Data Source loaded, setting states", 'color: #009900') - setStates(); - } else { - console.log("%c States Data Source is not loaded, adding event listener sourcedata", 'color: #009900') - console.log("A16") - mapgl.on('sourcedata', setAfterLoadStates); - } - } - - mapgl.on('style.load', () => { - console.log("A17") - mapgl.setFog({ - color: 'rgb(186, 210, 235)', // Lower atmosphere - 'high-color': 'rgb(36, 92, 223)', // Upper atmosphere - 'horizon-blend': 0.02, // Atmosphere thickness (default 0.2 at low zooms) - 'space-color': 'rgb(11, 11, 25)', // Background color - 'star-intensity': 0.6 // Background star brightness (default 0.35 at low zoooms ) - }); // Set the default atmosphere style - }); - - // Clear any errors from previous updates - this.clearErrors(queryResponse.fields); - - if (queryResponse.fields.measures.length == 0 || queryResponse.fields.dimensions.length == 0) { - this.addError({ title: "No Measures or Dimensions", message: "This chart requires a measure and a dimension." }); - return; - } - - console.log("query response") - console.log(queryResponse) - - done() - } -}); \ No newline at end of file diff --git a/src/visualizations/custom/maps/map_test copy.js b/src/visualizations/custom/maps/map_test copy.js deleted file mode 100644 index a310d8a..0000000 --- a/src/visualizations/custom/maps/map_test copy.js +++ /dev/null @@ -1,1431 +0,0 @@ -import mapboxgl from 'mapbox-gl'; -import stateData from './mapbox-boundaries-adm1-v3_4.json'; -import cbsaData from './mapbox-boundaries-sta2-v3_4.json'; -import zipData from './mapbox-boundaries-pos4-v3_4.json'; -import regeneratorRuntime from "regenerator-runtime"; -import bbox from '@turf/bbox'; -import "core-js/stable"; - -if (mapboxgl.version.indexOf('2.9.') === 0) Object.defineProperty(window, 'caches', { value: undefined }); - -function numberWithCommas(x) { - if(!x) return undefined; - x = x.toFixed(0); - var parts = x.toString().split("."); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); - return parts.join("."); -}; - -looker.plugins.visualizations.add({ - id: "thrive_custom_special_granularity_map", - label: "Custom Layered Mapbox Map", - // Set up the initial state of the visualization - create: function (element, config) { - // Insert a `; - - this.__currentLayer = "states-join"; - this.__LAYERNAMES = [ - { - name: "states-join", - groupingName: "State" - }, - { - name: "cbsas-join", - groupingName: "CBSA" - }, - { - name: "zips-join", - groupingName: "Zipcode" - } - ]; - - // Create the top layer selector bar - //###################################################################################// - - this.__mapStatesButton = document.createElement('button'); - this.__mapStatesButton.innerHTML = "States"; - this.__mapStatesButton.className = "map-paginator active"; - this.__mapStatesButton.id = "states-join"; - - this.__mapCBSAsButton = document.createElement('button'); - this.__mapCBSAsButton.innerHTML = "CBSAs"; - this.__mapCBSAsButton.className = "map-paginator"; - this.__mapCBSAsButton.id = "cbsas-join"; - - this.__mapZipCodesButton = document.createElement('button'); - this.__mapZipCodesButton.innerHTML = "Zip Codes"; - this.__mapZipCodesButton.className = "map-paginator"; - this.__mapZipCodesButton.id = "zips-join"; - - this.__mapPaginatorWrapper = document.createElement('div'); - this.__mapPaginatorWrapper.className = "map-paginator__wrapper"; - - this.__mapPaginatorWrapper.appendChild(this.__mapStatesButton); - this.__mapPaginatorWrapper.appendChild(this.__mapCBSAsButton); - this.__mapPaginatorWrapper.appendChild(this.__mapZipCodesButton); - - element.appendChild(this.__mapPaginatorWrapper); - - this.__selectedLocalesWrapper = document.createElement('div'); - this.__selectedLocalesWrapper.className = "selected-locales__wrapper"; - element.appendChild(this.__selectedLocalesWrapper); - - //###################################################################################// - - this.__mapBox = document.createElement('div'); - this.__mapBox.style.height = '100%'; - this.__mapBox.style.width = '100%'; - this.__mapBox.id = "map"; - - element.appendChild(this.__mapBox); - - mapboxgl.accessToken = 'pk.eyJ1IjoiZHVuY2FuY2ZyYXNlciIsImEiOiJjbDRvbDlmZWQwMGdzM2ZxazZybTVkdDQ0In0.xL5_LBkos5tYRbLxR0tQRQ'; - this.__map = new mapboxgl.Map({ - container: 'map', - style: 'mapbox://styles/mapbox/light-v10', - center: [-98.5795, 39.8283], - zoom: 4, - projection: 'globe' - }); - - this.__map.addControl(new mapboxgl.NavigationControl()); - this.__map.addControl(new mapboxgl.FullscreenControl()); - - this.__map.scrollZoom.disable(); - - this.__map.on('load', () => { - this.__map.addSource('statesData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-adm1-v3' - }).addSource('cbsaData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-sta2-v3' - }).addSource('zipData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-pos4-v3' - }); - }); - - - var link = document.createElement('link'); - link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css'; - link.rel = 'stylesheet'; - - var script = document.createElement('script'); - script.src = 'https://kit.fontawesome.com/f2060bf509.js'; - script.crossOrigin = "anonymous"; - - document.head.appendChild(link); - document.head.appendChild(script); - }, - - /* Render in response to the data or settings changing - Params: - @param data: array of returned data with fields - @param element: HTML DOM element where the vis is stored - @param config: Settings object on the tile where the vis is located - @param queryResponse: Entire response to query, includes data object and config object - @param details: ? - @param done: function to call to tell Looker you've finished rendering - */ - updateAsync: function (data, element, config, queryResponse, details, done) { - // Clear any errors from previous updates - this.clearErrors(queryResponse.fields); - - //When no dimensions and/or no measures are present the viz stops updating and displays an error - if (queryResponse.fields.measures.length < 1 || queryResponse.fields.dimensions.length < 1) { - this.addError({ title: "No Measures or Dimensions", message: "This chart requires a measure and a dimension." }); - return; - } - - /* Pass a message to the GTM Frontend to send a locale filter to looker - Params: - @param locales: object containing all filtered locales at different levels of granularity - */ - const throwMessage = (locales) => window.parent.parent.postMessage({ message: "crossFilterLocale", value: locales }, "*"); - /* Pass a message to the GTM Frontend to send a granularity grouping filter to looker - Params: - @param g: string representing new grouping to filter to - */ - const changeGranularity = (g) => window.parent.parent.postMessage({ message: "changeGranularity", value: g }, "*"); - - // Because `this` does not work properly in functions (`this` goes local), move the map to a more usable variable - let mapgl = this.__map; - // Find name of measure, as measure is requested KPI, which is dynamic, returns String - const measureName = queryResponse.fields.measures[0].name; - // Find label of measure, as measure is requested KPI, which is dynamic. This prints to UI, returns String - const measureLabel = queryResponse.fields.measures[0].label_short; - // Move HTML DOM element from `this` to permanent variable, returns HTML DOM element - const localesWrapper = this.__selectedLocalesWrapper; - // Move Object from `this` to permanent variable, returns Object - const thisLayerNames = this.__LAYERNAMES; - // Tracks feature currently being hovered, DEV NOTE: To increase conciseness this could be altered to `hoveredFeatureId`, set to null - let hoveredStateId = null; // Tracks hovered state and updates with popup - // The object responsible for capturing selected features, DEV NOTE: To increase conciseness this could be altered to `selectedFeatureNames`, set to object of empty arrays - let filteredStateNames = { - "states-join": [], - "cbsas-join": [], - "zips-join": [] - }; - - /* Creates legend for the map - Params: - @param data: contains current data object at runtime - @param fieldName: holds the current grouping - @param max: the maximum value currently represented on the map - */ - const createLegend = (data, fieldName, max) => { - // If the grouping is null, return. This keeps the legend in line with the active layer - if(determineNull(data, fieldName)) return; - // If there is an old legend, remove it before creating the new one. - const oldLegendBox = document.getElementById("mapboxLegend"); - if(oldLegendBox) oldLegendBox.parentNode.removeChild(oldLegendBox); - // Create the elements necessary to construct the legend, all below elements should return HTML DOM nodes - const legendBox = document.createElement("div"); - const legendLeft = document.createElement("div"); - const legendRight = document.createElement("div"); - const legendBar = document.createElement("div"); - const legendRightTop = document.createElement("div"); - const legendRightBottom = document.createElement("div"); - // Add the proper classes to the legend elements consistent with the CSS styling in `create()` - legendBox.className = "legend-box"; - legendLeft.className = "legend-left"; - legendRight.className = "legend-right"; - legendBar.className = "legend-bar"; - // Display the maximum value with the proper number of commas for a number it's length - legendRightTop.innerHTML = numberWithCommas(max); - // The lower bound is 1, since 0 is never displayed - legendRightBottom.innerHTML = 1; - // Append all the elements in order to create the legend from the CSS styling in `create()` - legendLeft.appendChild(legendBar); - legendRight.appendChild(legendRightTop); - legendRight.appendChild(legendRightBottom); - legendBox.appendChild(legendLeft); - legendBox.appendChild(legendRight); - // Give the entire legend an id so it can be deleted on refresh. - legendBox.id = "mapboxLegend"; - // Append the new legend to the entire visualization - element.appendChild(legendBox); - } - - const showHideMultiTooltip = () => document.getElementById("mainTooltip").classList.toggle("active") - - const createMultiTooltip = () => { - const mapboxbox = document.getElementById("map") - const tooltipOpener = mapboxbox.appendChild(document.createElement('div')) - const tooltipBackground = mapboxbox.appendChild(document.createElement("div")) - const tooltipOuter = tooltipBackground.appendChild(document.createElement("div")) - const tooltipInner = tooltipOuter.appendChild(document.createElement("div")) - const tooltipTop = tooltipOuter.appendChild(document.createElement("div")) - const tooltipBottom = tooltipOuter.appendChild(document.createElement("div")) - const tooltipTitle = tooltipTop.appendChild(document.createElement("h2")) - const tooltipDescription = tooltipTop.appendChild(document.createElement("p")) - const tooltipButton = tooltipBottom.appendChild(document.createElement("button")) - tooltipBackground.className = "ctrl-tooltip__background" - tooltipBackground.id = "mainTooltip" - tooltipOuter.className = "ctrl-tooltip" - tooltipInner.className = "ctrl-tooltip__inner" - tooltipTop.className = "ctrl-tooltip__top" - tooltipBottom.className = "ctrl-tooltip__bottom" - tooltipTitle.innerHTML = "Keyboard Shortcut" - tooltipDescription.innerHTML = "Press and hold the Ctrl key (or Cmd key on Macs) while clicking on different locations to select multiple at once." - tooltipButton.innerHTML = "Got It" - tooltipButton.addEventListener("click", showHideMultiTooltip) - tooltipOpener.className = "tooltip-opener" - tooltipOpener.innerHTML = '' - tooltipOpener.addEventListener("click", showHideMultiTooltip) - } - - /* Runs an update function when a user removes or adds a feature to the selection list. - Params: - @param element: The element that holds the feature pucks. - @param selectedLayer: The layer where the click originated or where the feature should be removed from. - */ - async function runSelectionUpdate(element, selectedLayer) { - // Holds the DOM element that is the lowest single parent, returns HTML DOM Element. - const prevParent = document.getElementById("selectedLocaleContainer"); - - const oldMoreBox = document.getElementById("moreBoxWrapper") - // If the lowest parent element exists, delete it so it can be replaced. - if(prevParent) element.removeChild(prevParent); - - if(oldMoreBox) element.removeChild(oldMoreBox) - // Create a new div to replace the last and give it an id for deletion later. - const parent = document.createElement("div"); - parent.id = "selectedLocaleContainer"; - // Use advanced filtering logic to determine all the values present and get a count - const prevalues = (Object.values(filteredStateNames)).map(element => element); - const values = [].concat.apply([], prevalues); - const count = values.length; - // Create a "See More" puck which users can click to display the hidden selected features. - const moreWrapper = document.createElement("div"); - // When the total number of objects is greater than 3, simply change the text of the puck to display the number of hidden features - const moreBoxWrapper = document.createElement("div"); - const moreBox = document.createElement("div"); - moreBox.className = "selected-locale__more-box"; - moreBox.id = "moreBox"; - moreBoxWrapper.appendChild(moreBox); - moreBoxWrapper.id = "moreBoxWrapper"; - moreBoxWrapper.className = "selected-locale__more-box__wrapper"; - element.appendChild(moreBoxWrapper); - - if(count > 3) { - moreWrapper.className = "selected-locale__more"; - const moreText = document.createElement("span"); - moreText.innerHTML = `+${count - 2} more locales`; - moreWrapper.appendChild(moreText); - }; - // Total Count will keep a live count of number of features, returns Integer - let totalCount = 0; - // Rotate through each layer using Object.keys to get the layer names - Object.keys(filteredStateNames).forEach((layer) => { - // Rotate Through each selected value on a given layer, also keep the index to determine from which position to splice on removal. - filteredStateNames[layer].forEach((feature, index) => { - // Increment total count to keep track of which elements should be hidden - totalCount++; - // Create each element in order to make a puck. In the else statement the pucks are not hidden. - const selectedLocale = document.createElement("div"); - const selectedLocaleText = document.createElement("span"); - const removeButton = document.createElement('div'); - removeButton.innerHTML = ''; - // Add an event listener that will remove a value from the list when the `x` is clicked - removeButton.addEventListener("click", () => { - // Remove a single element at the index supplied - filteredStateNames[layer].splice(index, 1); - // Update the selected locales on the GTM Dashboard - throwMessage(filteredStateNames); - // Rerun this function so that the pucks show the item is removed - runSelectionUpdate(element, selectedLayer); - }) - // Finish creating the puck and append it to the parent element. - selectedLocaleText.innerHTML = feature; - selectedLocale.appendChild(selectedLocaleText); - selectedLocale.appendChild(removeButton); - - if(totalCount > 2 && count > 3) { - selectedLocale.className = "selected-locale__hidden"; - moreBox.appendChild(selectedLocale) - } else { - selectedLocale.className = "selected-locale"; - parent.appendChild(selectedLocale); - } - }) - }) - // When the total number of features is empty, revert to the states dashboard, zoom back to center and reset zoom. - if(totalCount === 0) { - autoChangeActive("states-join"); - changeGranularity("State"); - mapgl.easeTo({ center: [-98.5795, 39.8283], zoom: 4, duration: 1000 }) - } - moreWrapper.addEventListener("click", function() { - if(moreWrapper.classList.contains("active")) { - runSelectionUpdate(element, selectedLayer) - } else { - moreWrapper.classList.toggle("active") - moreWrapper.innerHTML = "Close" - const elements = document.querySelectorAll(".selected-locale"); - const mbw = document.getElementById("moreBoxWrapper") - const mb = document.getElementById("moreBox") - //elements.forEach((puck) => puck.remove()) - elements.forEach((puck) => mb.appendChild(puck)) - mbw.classList.toggle("active") - } - }) - // Add the hidden puck to the selected features - parent.appendChild(moreWrapper); - // Append the whole selected locale box to the visualization - element.appendChild(parent); - }; - - /* Params: - @param e: mapBox click event - @param layerName: map layer from which mapBox click event originated - @param lookupData: object containing feature data - @param localesWrapper: HTML DOM element where pucks are located - @param replaceCommas: *optional* flag to replace commas in selected features Name - */ - function hostClickEvent(e, layerName, lookupData, localesWrapper, replaceCommas) { - // bbox represents the point on the map that was just clicked, returned in the form of longLat - const bbox = [[e.point.x, e.point.y], [e.point.x, e.point.y]]; - // selectedFeatures represents the features available at the point `bbox`, returns [featureA, featureB, etc...] - const selectedFeatures = mapgl.queryRenderedFeatures(bbox, {layers: [layerName]}); - // line below maps `name` properties from selectedFeatures above, returns [nameA, nameB, etc...] - const names = selectedFeatures.map((feature) => feature.state.name); - // Line below gets the first name (name of closest feature to bbox), returns string or undefined - var selectedFeatureName = names[0]; - // When selectedFeatureName is undefined, does not execute - if(selectedFeatureName) { - // If optional parameter `replaceCommas` is true, attempts to replace commas, upon failing executes console.warn - if(replaceCommas && selectedFeatureName) selectedFeatureName = selectedFeatureName.replace(",", "") - /* When the selectedFeatureName is not a property of lookupData or the - selectedFeatureName is already included in `filteredStateNames[layerName]` then early return */ - if(!lookupData.hasOwnProperty(selectedFeatureName) || filteredStateNames[layerName].includes(selectedFeatureName)) return; - /* Push the selectedFeatureName into the proper category of `filteredStateNames[layerName]`, - inline and returns {layerNameA: [featureA, featureB, etc...], layerNameB: [featureA, featureB, etc...]} , etc... */ - filteredStateNames[layerName].push(selectedFeatureName) - // Updates the pucks that show feature selection bottom-right of the map, returns null - runSelectionUpdate(localesWrapper, layerName); - // If the ctrl key is not selected, send data for filter update - if(!e.originalEvent.ctrlKey) { - // Send data to GTM Frontend for a looker data refresh, returns null - throwMessage(filteredStateNames) - // Get layer names from filteredStateNames with Object.keys(), Returns array: ["layerX", "layerY", "layerZ"] - const layers = Object.keys(filteredStateNames) - // Check what the next layer in the list is after the layer currently in use, returns String or undefined - const nextLayerName = layers[layers.findIndex((element) => element === layerName) + 1] - // If nextLayerName is not undefined get the grouping name and change granularity to next lowest setting - if(nextLayerName) { - // Get the grouping name of the next layer, returns String no undefined - const nextLayerGrouping = thisLayerNames[thisLayerNames.findIndex((element) => element.name === nextLayerName)].groupingName - // Update the selected grouping in the GTM Frontend to swap granularities - changeGranularity(nextLayerGrouping) - // Automatically change the active layer to the specific layer supplied - autoChangeActive(nextLayerName) - } - } - } - } - - // When the map loads, create each initial visualization regardless of the existence of data - mapgl.on('load', () => { - createStatesViz(); - createCBSAsViz(); - createZipsViz(); - //createMultiTooltip(); - //showHideMultiTooltip(); - }); - - // When the map goes idle, attach an event listener to the granularity buttons to change to that granularity - mapgl.on('idle', () => { - this.__mapStatesButton.addEventListener("click", changeActive); - this.__mapCBSAsButton.addEventListener("click", changeActive); - this.__mapZipCodesButton.addEventListener("click", changeActive); - }) - - /* As Compared to the next function, this function automatically changes which layer is active rather than determining - What was clicked. - Params: - @param layer: The name of the layer that the visualization will swap to. - */ - const autoChangeActive = (layer) => { - // Get all elements with the given class, this will return a collection of DOM elements that are the buttons - const els = document.getElementsByClassName("map-paginator"); - // Loop through the - for (let i = 0; i < els.length; i++) { - if(els[i].classList.contains("active")) els[i].classList.remove("active"); - if(els[i].id === layer) els[i].classList.add("active") - }; - - for (let j = 0; j < this.__LAYERNAMES.length; j++) { - if(this.__LAYERNAMES[j].name !== layer) mapgl.setLayoutProperty(this.__LAYERNAMES[j].name, 'visibility', 'none'); - }; - - const filteredDown = this.__LAYERNAMES.filter(item => item.name === layer) - if(filteredDown.length > 0) mapgl.setLayoutProperty(layer, 'visibility', 'visible'); - } - - const changeActive = (e) => { - if(!e.target.classList.contains("active")) { - // Updates the nav-bar active status - const els = document.getElementsByClassName("map-paginator"); - for (let i = 0; i < els.length; i++) { - if(els[i].classList.contains("active")) { - els[i].classList.remove("active"); - }; - }; - - e.target.classList.add("active"); - - // Updates the layers' active status - for (let j = 0; j < this.__LAYERNAMES.length; j++) { - if(this.__LAYERNAMES[j].name !== e.target.id)mapgl.setLayoutProperty(this.__LAYERNAMES[j].name, 'visibility', 'none'); - }; - - const filteredDown = this.__LAYERNAMES.filter(item => item.name === e.target.id) - - if(filteredDown.length > 0) { - mapgl.setLayoutProperty(e.target.id, 'visibility', 'visible'); - changeGranularity(filteredDown[0].groupingName) - } - - } - } - - function getMaxState(arr) { - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.state"].value)); - try { - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function getMaxCBSA(arr) { - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.cbsa"].value)); - try { - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function getMaxZip(arr) { - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.zip"].value)); - try { - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function determineNull(arr, prop) { - return arr.every(row => row[prop].value === null); - } - - function createStatesViz() { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - - const searchData = stateData.adm1.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['name']] = featureData - } - }) - return lookupData; - } - const maxValue = getMaxState(lookupData) - - mapgl.addLayer({ - id: 'states-join', - type: 'fill', - source: 'statesData', - 'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - }, - 'source-layer': 'boundaries_admin_1', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - mapgl.on('mousemove', 'states-join', (e) => { - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const state = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove; - return; - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'states-join', () => { - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: false } - ); - popup.remove(); - } - hoveredStateId = null; - }); - - mapgl.on('click', 'states-join', (e) => hostClickEvent(e, 'states-join', lookupData, localesWrapper)); - - function setStates() { - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.state"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "statesData", - sourceLayer: 'boundaries_admin_1', - id: lookupData[row["dim_zi_map_vis.state"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - fipsCode: lookupData[row["dim_zi_map_vis.state"].value].unit_code, - name: lookupData[row["dim_zi_map_vis.state"].value].name, - hovered: false - } - ) - } - } - - createLegend(data, "dim_zi_map_vis.state", maxValue) - - // Check if `statesData` source is loaded. - function setAfterLoadStates(event) { - if (event.sourceID !== 'statesData' && !event.isSourceLoaded) return; - setStates(); - mapgl.off('sourcedata', setAfterLoadStates); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('statesData')) { - setStates(); - } else { - mapgl.on('sourcedata', setAfterLoadStates); - } - } - - - function createCBSAsViz() { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - - const searchData = cbsaData.sta2.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['name'].replace(",", "")] = featureData - } - }) - return lookupData; - } - - - - const maxValue = getMaxCBSA(lookupData) - - mapgl.addLayer({ - id: 'cbsas-join', - type: 'fill', - source: 'cbsaData', - 'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - }, - 'source-layer': 'boundaries_stats_2', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - - mapgl.on('mousemove', 'cbsas-join', (e) => { - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const cbsa = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'cbsaData', id: hoveredStateId, sourceLayer: 'boundaries_stats_2' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'cbsaData', id: hoveredStateId, sourceLayer: 'boundaries_stats_2' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'cbsas-join', () => { - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'cbsaData', id: hoveredStateId, sourceLayer: 'boundaries_stats_2' }, - { hover: false } - ); - popup.remove(); - } - hoveredStateId = null; - }); - - mapgl.on('click', 'cbsas-join', (e) => hostClickEvent(e, 'cbsas-join', lookupData, localesWrapper, true)); - - function setStates() { - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.cbsa"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "cbsaData", - sourceLayer: 'boundaries_stats_2', - id: lookupData[row["dim_zi_map_vis.cbsa"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.cbsa"].value].name, - hovered: false - } - ) - } - } - - createLegend(data, "dim_zi_map_vis.cbsa", maxValue) - - // Check if `statesData` source is loaded. - function setAfterLoadCBSAs(event) { - if (event.sourceID !== 'cbsaData' && !event.isSourceLoaded) return; - setStates(); - mapgl.off('sourcedata', setAfterLoadCBSAs); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('cbsaData')) { - setStates(); - } else { - mapgl.on('sourcedata', setAfterLoadCBSAs); - } - } - - function createZipsViz() { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - - const searchData = zipData.pos4.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['unit_code']] = featureData - } - }) - return lookupData; - } - - const maxValue = getMaxZip(lookupData) - - mapgl.addLayer({ - id: 'zips-join', - type: 'fill', - source: 'zipData', - 'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - }, - 'source-layer': 'boundaries_postal_4', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - - mapgl.on('mousemove', 'zips-join', (e) => { - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const zip = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'zipData', id: hoveredStateId, sourceLayer: 'boundaries_postal_4' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'zipData', id: hoveredStateId, sourceLayer: 'boundaries_postal_4' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'zips-join', () => { - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'zipData', id: hoveredStateId, sourceLayer: 'boundaries_postal_4' }, - { hover: false } - ); - popup.remove(); - } - hoveredStateId = null; - }); - - mapgl.on('click', 'zips-join', (e) => hostClickEvent(e, 'zips-join', lookupData, localesWrapper)); - - function setStates() { - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.zip"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "zipData", - sourceLayer: 'boundaries_postal_4', - id: lookupData[row["dim_zi_map_vis.zip"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.zip"].value].unit_code, - hovered: false - } - ) - } - } - - createLegend(data, "dim_zi_map_vis.zip", maxValue) - - // Check if `statesData` source is loaded. - function setAfterLoadZips(event) { - if (event.sourceID !== 'zipData' && !event.isSourceLoaded) return; - setStates(); - mapgl.off('sourcedata', setAfterLoadZips); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('zipData')) { - setStates(); - } else { - mapgl.on('sourcedata', setAfterLoadZips); - } - } - - mapgl.on('style.load', () => { - mapgl.setFog({ - 'color': 'rgb(186, 210, 235)', // Lower atmosphere - 'high-color': 'rgb(36, 92, 223)', // Upper atmosphere - 'horizon-blend': 0.02, // Atmosphere thickness (default 0.2 at low zooms) - 'space-color': 'rgb(11, 11, 25)', // Background color - 'star-intensity': 0.6 // Background star brightness (default 0.35 at low zoooms ) - }); // Set the default atmosphere style - }); - - function autozoomCBSA() { - if(mapgl.getSource('cbsaData') && mapgl.isSourceLoaded('cbsaData')) { - const fu = mapgl.queryRenderedFeatures({ layers: ['cbsas-join'] }) - //const fu = mapgl.querySourceFeatures('cbsaData', { sourceLayer: 'boundaries_stats_2' }) - const f = fu.filter((feature) => feature.state && feature.state.requestedKPI) - if (f.length > 0) { - var bb = bbox({ type: 'FeatureCollection', features: f }); - mapgl.fitBounds(bb, {padding: 50}); - mapgl.off("idle", autozoomCBSA) - } - } - } - - function autozoomZip() { - if(mapgl.getSource('zipData') && mapgl.isSourceLoaded('zipData')) { - const fu = mapgl.queryRenderedFeatures({ layers: ['zips-join'] }) - //const fu = mapgl.querySourceFeatures('cbsaData', { sourceLayer: 'boundaries_stats_2' }) - const f = fu.filter((feature) => feature.state && feature.state.requestedKPI) - if (f.length > 0) { - var bb = bbox({ type: 'FeatureCollection', features: f }); - mapgl.fitBounds(bb, {padding: 50}); - mapgl.off("idle", autozoomZip) - } - } - } - - runVisUpdate() - - function runVisUpdate() { - mapgl.on("idle", autozoomCBSA) - mapgl.on("idle", autozoomZip) - - const updateStates = () => { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - const searchData = stateData.adm1.data.all; - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') lookupData[featureData['name']] = featureData - }) - return lookupData; - } - - mapgl.removeFeatureState({source: "statesData", sourceLayer: 'boundaries_admin_1'}) - - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.state"].value)) continue; - mapgl.setFeatureState( - { - source: "statesData", - sourceLayer: 'boundaries_admin_1', - id: lookupData[row["dim_zi_map_vis.state"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - fipsCode: lookupData[row["dim_zi_map_vis.state"].value].unit_code, - name: lookupData[row["dim_zi_map_vis.state"].value].name, - hovered: false - } - ) - } - - const maxValue = getMaxState(lookupData) - createLegend(data, "dim_zi_map_vis.state", maxValue) - - mapgl.setPaintProperty( - 'states-join', - 'fill-color', - ['case', ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'],'rgba(255, 255, 255, 0)'] - ) - } - - const updateCBSAs = () => { - console.log("Updating CBSA's", data) - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - const searchData = cbsaData.sta2.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') lookupData[featureData['name'].replace(",", "")] = featureData - }) - return lookupData; - } - - mapgl.removeFeatureState({source: "cbsaData", sourceLayer: 'boundaries_stats_2'}) - - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.cbsa"].value)) continue; - mapgl.setFeatureState( - { - source: "cbsaData", - sourceLayer: 'boundaries_stats_2', - id: lookupData[row["dim_zi_map_vis.cbsa"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.cbsa"].value].name, - hovered: false - } - ) - } - - const maxValue = getMaxCBSA(lookupData) - createLegend(data, "dim_zi_map_vis.cbsa", maxValue) - - mapgl.setPaintProperty( - 'cbsas-join', - 'fill-color', - ['case', ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'],'rgba(255, 255, 255, 0)'] - ) - } - - const updateZips = () => { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - const searchData = zipData.pos4.data.all; - - Object.keys(searchData).forEach(function (key) { - const featureData = searchData[key]; - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['unit_code']] = featureData; - } - }) - return lookupData; - } - - mapgl.removeFeatureState({source: "zipData", sourceLayer: 'boundaries_postal_4'}); - - for (let i = 0; i < data.length; i++) { - const row = data[i]; - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.zip"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "zipData", - sourceLayer: 'boundaries_postal_4', - id: lookupData[row["dim_zi_map_vis.zip"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.zip"].value].unit_code, - hovered: false - } - ) - } - - const maxValue = getMaxZip(lookupData); - - createLegend(data, "dim_zi_map_vis.zip", maxValue); - - mapgl.setPaintProperty( - 'zips-join', - 'fill-color', - ['case', ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'],'rgba(255, 255, 255, 0)'] - ); - } - - if(mapgl.getSource('statesData') && mapgl.isSourceLoaded('statesData')) updateStates(); - if(mapgl.getSource('cbsaData') && mapgl.isSourceLoaded('cbsaData')) updateCBSAs(); - if(mapgl.getSource('zipData') && mapgl.isSourceLoaded('zipData')) updateZips(); - } - - done() - } -}); \ No newline at end of file diff --git a/src/visualizations/custom/maps/map_test.js b/src/visualizations/custom/maps/map_test.js index cfea6b8..d9b1cef 100644 --- a/src/visualizations/custom/maps/map_test.js +++ b/src/visualizations/custom/maps/map_test.js @@ -6,22 +6,27 @@ import regeneratorRuntime from "regenerator-runtime"; import bbox from '@turf/bbox'; import "core-js/stable"; +// DEV NOTE: Mikel from mapbox asked me to add this to destroy a non-blocking recurring console error if (mapboxgl.version.indexOf('2.9.') === 0) Object.defineProperty(window, 'caches', { value: undefined }); -function numberWithCommas(x) { - if(!x) return undefined; - x = x.toFixed(0); - var parts = x.toString().split("."); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); - return parts.join("."); -}; +/* DEV NOTE: In an attempt to make this function run as fast as possible, I switch from using `.toString()` to using '' + x which is 8% faster , +This function is used to convert long integers into strings with commas between every three numbers in reverse. +Params: + @param x: a number, specifically an integer +*/ +const numberWithCommas = (x) => x ? (('' + x.toFixed(0)).split(".")[0]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : undefined; +// Add the visualization to the looker plugins, self-explanatory. This object is required for all custom visualizations and should follow this top level layout looker.plugins.visualizations.add({ id: "thrive_custom_special_granularity_map", label: "Custom Layered Mapbox Map", // Set up the initial state of the visualization create: function (element, config) { - // Insert a `; - - this.__currentLayer = "states-join"; + + // This is an object containing the information to refer to for groupingNames and layerNames this.__LAYERNAMES = [ - { - name: "states-join", - groupingName: "State" - }, - { - name: "cbsas-join", - groupingName: "CBSA" - }, - { - name: "zips-join", - groupingName: "Zipcode" - } + { name: "states-join", groupingName: "State" }, + { name: "cbsas-join", groupingName: "CBSA" }, + { name: "zips-join", groupingName: "Zipcode" } ]; - - // Create the top layer selector bar - //###################################################################################// - - this.__mapStatesButton = document.createElement('button'); + // Create and style all the elements that make up the major components of the map and surrounding UI. + this.__mapPaginatorWrapper = element.appendChild(document.createElement('div')); + this.__selectedLocalesWrapper = element.appendChild(document.createElement('div')); + this.__mapBox = element.appendChild(document.createElement('div')); + this.__mapStatesButton = this.__mapPaginatorWrapper.appendChild(document.createElement('button')); + this.__mapCBSAsButton = this.__mapPaginatorWrapper.appendChild(document.createElement('button')); + this.__mapZipCodesButton = this.__mapPaginatorWrapper.appendChild(document.createElement('button')); + this.__mapPaginatorWrapper.className = "map-paginator__wrapper"; + this.__selectedLocalesWrapper.className = "selected-locales__wrapper"; this.__mapStatesButton.innerHTML = "States"; this.__mapStatesButton.className = "map-paginator active"; this.__mapStatesButton.id = "states-join"; - - this.__mapCBSAsButton = document.createElement('button'); this.__mapCBSAsButton.innerHTML = "CBSAs"; this.__mapCBSAsButton.className = "map-paginator"; this.__mapCBSAsButton.id = "cbsas-join"; - - this.__mapZipCodesButton = document.createElement('button'); this.__mapZipCodesButton.innerHTML = "Zip Codes"; this.__mapZipCodesButton.className = "map-paginator"; this.__mapZipCodesButton.id = "zips-join"; - - this.__mapPaginatorWrapper = document.createElement('div'); - this.__mapPaginatorWrapper.className = "map-paginator__wrapper"; - - this.__mapPaginatorWrapper.appendChild(this.__mapStatesButton); - this.__mapPaginatorWrapper.appendChild(this.__mapCBSAsButton); - this.__mapPaginatorWrapper.appendChild(this.__mapZipCodesButton); - - element.appendChild(this.__mapPaginatorWrapper); - - this.__selectedLocalesWrapper = document.createElement('div'); - this.__selectedLocalesWrapper.className = "selected-locales__wrapper"; - element.appendChild(this.__selectedLocalesWrapper); - - //###################################################################################// - - this.__mapBox = document.createElement('div'); this.__mapBox.style.height = '100%'; this.__mapBox.style.width = '100%'; this.__mapBox.id = "map"; - element.appendChild(this.__mapBox); - + // Set the access Token to access the Mapbox GL API mapboxgl.accessToken = 'pk.eyJ1IjoiZHVuY2FuY2ZyYXNlciIsImEiOiJjbDRvbDlmZWQwMGdzM2ZxazZybTVkdDQ0In0.xL5_LBkos5tYRbLxR0tQRQ'; + // Initialize the map with the light style, center it on the USA, set a minumum zoom level and turn off scroll interaction. this.__map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/light-v10', @@ -365,35 +344,18 @@ looker.plugins.visualizations.add({ zoom: 3.6, minZoom: 2, scrollZoom: false, - //projection: 'globe' + dragRotate: false, + keyboard: false, + maxPitch: 0, + touchPitch: false }); - + // Add the +/- Map zoom controls. This also adds a rotation control though it's not super useful this.__map.addControl(new mapboxgl.NavigationControl()); - - this.__map.on('load', () => { - this.__map.addSource('statesData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-adm1-v3' - }).addSource('cbsaData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-sta2-v3' - }).addSource('zipData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-pos4-v3' - }); - }); - - - var link = document.createElement('link'); - link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css'; - link.rel = 'stylesheet'; - - var script = document.createElement('script'); + // When the map first loads, add all the sources which dictate the behavior of data added to the map + this.__map.on('load', () => this.__map.addSource('statesData', { type: 'vector', url: 'mapbox://mapbox.boundaries-adm1-v3'}).addSource('cbsaData', { type: 'vector', url: 'mapbox://mapbox.boundaries-sta2-v3' }).addSource('zipData', { type: 'vector', url: 'mapbox://mapbox.boundaries-pos4-v3' })); + var script = document.head.appendChild(document.createElement('script')); script.src = 'https://kit.fontawesome.com/f2060bf509.js'; script.crossOrigin = "anonymous"; - - document.head.appendChild(link); - document.head.appendChild(script); }, /* Render in response to the data or settings changing @@ -406,7 +368,6 @@ looker.plugins.visualizations.add({ @param done: function to call to tell Looker you've finished rendering */ updateAsync: function (data, element, config, queryResponse, details, done) { - console.log("Updating in async", queryResponse) // Clear any errors from previous updates this.clearErrors(queryResponse.fields); @@ -674,11 +635,7 @@ looker.plugins.visualizations.add({ }); // When the map goes idle, attach an event listener to the granularity buttons to change to that granularity - mapgl.on('idle', () => { - this.__mapStatesButton.addEventListener("click", changeActive); - this.__mapCBSAsButton.addEventListener("click", changeActive); - this.__mapZipCodesButton.addEventListener("click", changeActive); - }) + mapgl.on('idle', () => document.querySelectorAll('.map-paginator').forEach(button => button.addEventListener("click", changeActive))) /* As Compared to the next function, this function automatically changes which layer is active rather than determining What was clicked. diff --git a/src/visualizations/custom/maps/map_test_old.js b/src/visualizations/custom/maps/map_test_old.js deleted file mode 100644 index d802b40..0000000 --- a/src/visualizations/custom/maps/map_test_old.js +++ /dev/null @@ -1,1223 +0,0 @@ -import mapboxgl from 'mapbox-gl'; -import stateData from './mapbox-boundaries-adm1-v3_4.json'; -import cbsaData from './mapbox-boundaries-sta2-v3_4.json'; -import zipData from './mapbox-boundaries-pos4-v3_4.json'; -import regeneratorRuntime from "regenerator-runtime"; -import bbox from '@turf/bbox'; -import "core-js/stable"; - -if (mapboxgl.version.indexOf('2.9.') === 0) Object.defineProperty(window, 'caches', { value: undefined }); - -function numberWithCommas(x) { - if(!x) return undefined; - x = x.toFixed(0); - var parts = x.toString().split("."); - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); - return parts.join("."); -}; - -looker.plugins.visualizations.add({ - id: "thrive_custom_special_granularity_map", - label: "Custom Layered Mapbox Map", - // Set up the initial state of the visualization - create: function (element, config) { - // Insert a `; - - this.__currentLayer = "states-join"; - this.__LAYERNAMES = [ - { - name: "states-join", - groupingName: "State" - }, - { - name: "cbsas-join", - groupingName: "CBSA" - }, - { - name: "zips-join", - groupingName: "Zipcode" - } - ]; - - // Create the top layer selector bar - //###################################################################################// - - this.__mapStatesButton = document.createElement('button'); - this.__mapStatesButton.innerHTML = "States"; - this.__mapStatesButton.className = "map-paginator active"; - this.__mapStatesButton.id = "states-join"; - - this.__mapCBSAsButton = document.createElement('button'); - this.__mapCBSAsButton.innerHTML = "CBSAs"; - this.__mapCBSAsButton.className = "map-paginator"; - this.__mapCBSAsButton.id = "cbsas-join"; - - this.__mapZipCodesButton = document.createElement('button'); - this.__mapZipCodesButton.innerHTML = "Zip Codes"; - this.__mapZipCodesButton.className = "map-paginator"; - this.__mapZipCodesButton.id = "zips-join"; - - this.__mapPaginatorWrapper = document.createElement('div'); - this.__mapPaginatorWrapper.className = "map-paginator__wrapper"; - - this.__mapPaginatorWrapper.appendChild(this.__mapStatesButton); - this.__mapPaginatorWrapper.appendChild(this.__mapCBSAsButton); - this.__mapPaginatorWrapper.appendChild(this.__mapZipCodesButton); - - element.appendChild(this.__mapPaginatorWrapper); - - this.__selectedLocalesWrapper = document.createElement('div'); - this.__selectedLocalesWrapper.className = "selected-locales__wrapper"; - element.appendChild(this.__selectedLocalesWrapper); - - //###################################################################################// - - this.__mapBox = document.createElement('div'); - this.__mapBox.style.height = '100%'; - this.__mapBox.style.width = '100%'; - this.__mapBox.id = "map"; - - element.appendChild(this.__mapBox); - - mapboxgl.accessToken = 'pk.eyJ1IjoiZHVuY2FuY2ZyYXNlciIsImEiOiJjbDRvbDlmZWQwMGdzM2ZxazZybTVkdDQ0In0.xL5_LBkos5tYRbLxR0tQRQ'; - this.__map = new mapboxgl.Map({ - container: 'map', - style: 'mapbox://styles/mapbox/light-v10', - center: [-98.5795, 39.8283], - zoom: 4, - projection: 'globe' - }); - - this.__map.addControl(new mapboxgl.NavigationControl()); - this.__map.addControl(new mapboxgl.FullscreenControl()); - - this.__map.on('load', () => { - this.__map.addSource('statesData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-adm1-v3' - }).addSource('cbsaData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-sta2-v3' - }).addSource('zipData', { - type: 'vector', - url: 'mapbox://mapbox.boundaries-pos4-v3' - }); - }); - - - var link = document.createElement('link'); - link.href = 'https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css'; - link.rel = 'stylesheet'; - - var script = document.createElement('script'); - script.src = 'https://kit.fontawesome.com/f2060bf509.js'; - script.crossOrigin = "anonymous"; - - document.head.appendChild(link); - document.head.appendChild(script); - - console.log("Create function complete"); - }, - - /* Render in response to the data or settings changing - Params: - @param data: array of returned data with fields - @param element: HTML DOM element where the vis is stored - @param config: Settings object on the tile where the vis is located - @param queryResponse: Entire response to query, includes data object and config object - @param details: ? - @param done: function to call to tell Looker you've finished rendering - */ - updateAsync: function (data, element, config, queryResponse, details, done) { - // Clear any errors from previous updates - this.clearErrors(queryResponse.fields); - - //When no dimensions and/or no measures are present the viz stops updating and displays an error - if (queryResponse.fields.measures.length < 1 || queryResponse.fields.dimensions.length < 1) { - this.addError({ title: "No Measures or Dimensions", message: "This chart requires a measure and a dimension." }); - return; - } - - /* Pass a message to the GTM Frontend to send a locale filter to looker - Params: - @param locales: object containing all filtered locales at different levels of granularity - */ - const throwMessage = (locales) => window.parent.parent.postMessage({ message: "crossFilterLocale", value: locales }, "*"); - /* Pass a message to the GTM Frontend to send a granularity grouping filter to looker - Params: - @param g: string representing new grouping to filter to - */ - const changeGranularity = (g) => window.parent.parent.postMessage({ message: "changeGranularity", value: g }, "*"); - - // Because `this` does not work properly in functions (`this` goes local), move the map to a more usable variable - let mapgl = this.__map; - // Find name of measure, as measure is requested KPI, which is dynamic, returns String - const measureName = queryResponse.fields.measures[0].name; - // Find label of measure, as measure is requested KPI, which is dynamic. This prints to UI, returns String - const measureLabel = queryResponse.fields.measures[0].label_short; - // Move HTML DOM element from `this` to permanent variable, returns HTML DOM element - const localesWrapper = this.__selectedLocalesWrapper; - // Move Object from `this` to permanent variable, returns Object - const thisLayerNames = this.__LAYERNAMES; - // Tracks feature currently being hovered, DEV NOTE: To increase conciseness this could be altered to `hoveredFeatureId`, set to null - let hoveredStateId = null; // Tracks hovered state and updates with popup - // The object responsible for capturing selected features, DEV NOTE: To increase conciseness this could be altered to `selectedFeatureNames`, set to object of empty arrays - let filteredStateNames = { - "states-join": [], - "cbsas-join": [], - "zips-join": [] - }; - - /* Creates legend for the map - Params: - @param data: contains current data object at runtime - @param fieldName: holds the current grouping - @param max: the maximum value currently represented on the map - */ - const createLegend = (data, fieldName, max) => { - // If the grouping is null, return. This keeps the legend in line with the active layer - if(determineNull(data, fieldName)) return; - // If there is an old legend, remove it before creating the new one. - const oldLegendBox = document.getElementById("mapboxLegend"); - if(oldLegendBox) oldLegendBox.parentNode.removeChild(oldLegendBox); - // Create the elements necessary to construct the legend, all below elements should return HTML DOM nodes - const legendBox = document.createElement("div"); - const legendLeft = document.createElement("div"); - const legendRight = document.createElement("div"); - const legendBar = document.createElement("div"); - const legendRightTop = document.createElement("div"); - const legendRightBottom = document.createElement("div"); - // Add the proper classes to the legend elements consistent with the CSS styling in `create()` - legendBox.className = "legend-box"; - legendLeft.className = "legend-left"; - legendRight.className = "legend-right"; - legendBar.className = "legend-bar"; - // Display the maximum value with the proper number of commas for a number it's length - legendRightTop.innerHTML = numberWithCommas(max); - // The lower bound is 1, since 0 is never displayed - legendRightBottom.innerHTML = 1; - // Append all the elements in order to create the legend from the CSS styling in `create()` - legendLeft.appendChild(legendBar); - legendRight.appendChild(legendRightTop); - legendRight.appendChild(legendRightBottom); - legendBox.appendChild(legendLeft); - legendBox.appendChild(legendRight); - // Give the entire legend an id so it can be deleted on refresh. - legendBox.id = "mapboxLegend"; - // Append the new legend to the entire visualization - element.appendChild(legendBox); - } - - /* Runs an update function when a user removes or adds a feature to the selection list. - Params: - @param element: The element that holds the feature pucks. - @param selectedLayer: The layer where the click originated or where the feature should be removed from. - */ - async function runSelectionUpdate(element, selectedLayer) { - // Holds the DOM element that is the lowest single parent, returns HTML DOM Element. - const prevParent = document.getElementById("selectedLocaleContainer"); - // If the lowest parent element exists, delete it so it can be replaced. - if(prevParent) element.removeChild(prevParent); - // Create a new div to replace the last and give it an id for deletion later. - const parent = document.createElement("div"); - parent.id = "selectedLocaleContainer"; - // Use advanced filtering logic to determine all the values present and get a count - const prevalues = (Object.values(filteredStateNames)).map(element => element); - const values = [].concat.apply([], prevalues); - const count = values.length; - // Create a "See More" puck which users can click to display the hidden selected features. - const moreWrapper = document.createElement("div"); - // When the total number of objects is greater than 3, simply change the text of the puck to display the number of hidden features - if(count > 3) { - moreWrapper.className = "selected-locale__more"; - const moreText = document.createElement("span"); - moreText.innerHTML = `+${count - 2} more locales`; - moreWrapper.appendChild(moreText); - }; - - // Total Count will keep a live count of number of features, returns Integer - let totalCount = 0; - Object.keys(filteredStateNames).forEach((layer) => { - filteredStateNames[layer].forEach((feature, index) => { - totalCount++; - if(totalCount > 2 && count > 3) { - // Here is where we'll create new hidden pucks and append them to a hidden box, for now do nothing - console.debug("Nothing"); - } else { - const selectedLocale = document.createElement("div"); - const selectedLocaleText = document.createElement("span"); - const removeButton = document.createElement('div'); - removeButton.innerHTML = ''; - - removeButton.addEventListener("click", () => { - filteredStateNames[layer].splice(index, 1); - throwMessage(filteredStateNames); - runSelectionUpdate(element, selectedLayer); - }) - - selectedLocaleText.innerHTML = feature; - selectedLocale.className = "selected-locale"; - - selectedLocale.appendChild(selectedLocaleText); - selectedLocale.appendChild(removeButton); - parent.appendChild(selectedLocale); - } - }) - }) - - if(totalCount === 0) { - autoChangeActive("states-join"); - changeGranularity("State"); - mapgl.zoomTo(4, { duration: 1000, offset: [100, 50] }); - mapgl.panTo([-98.5795, 39.8283], { duration: 1000 }); - } - - parent.appendChild(moreWrapper); - element.appendChild(parent); - }; - - /* Params: - @param e: mapBox click event - @param layerName: map layer from which mapBox click event originated - @param lookupData: object containing feature data - @param localesWrapper: HTML DOM element where pucks are located - @param replaceCommas: *optional* flag to replace commas in selected features Name - */ - function hostClickEvent(e, layerName, lookupData, localesWrapper, replaceCommas) { - // bbox represents the point on the map that was just clicked, returned in the form of longLat - const bbox = [[e.point.x, e.point.y], [e.point.x, e.point.y]]; - // selectedFeatures represents the features available at the point `bbox`, returns [featureA, featureB, etc...] - const selectedFeatures = mapgl.queryRenderedFeatures(bbox, {layers: [layerName]}); - // line below maps `name` properties from selectedFeatures above, returns [nameA, nameB, etc...] - const names = selectedFeatures.map((feature) => feature.state.name); - // Line below gets the first name (name of closest feature to bbox), returns string or undefined - var selectedFeatureName = names[0]; - - // When selectedFeatureName is undefined, does not execute - if(selectedFeatureName) { - // If optional parameter `replaceCommas` is true, attempts to replace commas, upon failing executes console.warn - if(replaceCommas) { - try { - // Replace Commas (particularly in CBSAs for comparison), returns string [no undefined] - selectedFeatureName = selectedFeatureName.replace(",", "") - } catch (error) { - // Warns when replace fails, NOTE: may be able to remove - console.warn("Could not replace null selection: " + selectedFeatureName) - } - } - - /* When the selectedFeatureName is not a property of lookupData or the - selectedFeatureName is already included in `filteredStateNames[layerName]` then early return */ - if(!lookupData.hasOwnProperty(selectedFeatureName) || filteredStateNames[layerName].includes(selectedFeatureName)) return; - - /* Push the selectedFeatureName into the proper category of `filteredStateNames[layerName]`, - inline and returns {layerNameA: [featureA, featureB, etc...], layerNameB: [featureA, featureB, etc...]} , etc... */ - filteredStateNames[layerName].push(selectedFeatureName) - // Updates the pucks that show feature selection bottom-right of the map, returns null - runSelectionUpdate(localesWrapper, layerName); - - // If the ctrl key is not selected, send data for filter update - if(!e.originalEvent.ctrlKey) { - // Send data to GTM Frontend for a looker data refresh, returns null - throwMessage(filteredStateNames) - const layers = Object.keys(filteredStateNames) - const nextLayerName = layers[layers.findIndex((element) => element === layerName) + 1] - if(nextLayerName) { - const nextLayerGrouping = thisLayerNames[thisLayerNames.findIndex((element) => {console.log("nln", nextLayerName); console.log("element", element.name); return element.name === nextLayerName})].groupingName - autoChangeActive(nextLayerName) - changeGranularity(nextLayerGrouping) - } - } - } - } - - mapgl.on('load', () => { - createStatesViz(); - createCBSAsViz(); - createZipsViz(); - }); - - mapgl.on('idle', () => { - this.__mapStatesButton.addEventListener("click", changeActive); - this.__mapCBSAsButton.addEventListener("click", changeActive); - this.__mapZipCodesButton.addEventListener("click", changeActive); - }) - - const autoChangeActive = (layer) => { - /* For reference, e.target is one of the buttons on top of the map. - The event listeners are added in a map.on('idle', () => {}) but have - previously been added in map.on('load', () => {}) to the same effect*/ - const els = document.getElementsByClassName("map-paginator"); - for (let i = 0; i < els.length; i++) { - if(els[i].classList.contains("active")) { - els[i].classList.remove("active"); - }; - - if(els[i].id === layer) els[i].classList.add("active") - }; - - for (let j = 0; j < this.__LAYERNAMES.length; j++) { - if(this.__LAYERNAMES[j].name !== layer) { - mapgl.setLayoutProperty(this.__LAYERNAMES[j].name, 'visibility', 'none'); - }; - }; - - const filteredDown = this.__LAYERNAMES.filter(item => item.name === layer) - if(filteredDown.length > 0) mapgl.setLayoutProperty(layer, 'visibility', 'visible'); - } - - const changeActive = (e) => { - /* For reference, e.target is one of the buttons on top of the map. - The event listeners are added in a map.on('idle', () => {}) but have - previously been added in map.on('load', () => {}) to the same effect*/ - - //e.preventDefault(); - //e.stopPropagation(); - - if(!e.target.classList.contains("active")) { - // Updates the nav-bar active status - const els = document.getElementsByClassName("map-paginator"); - for (let i = 0; i < els.length; i++) { - if(els[i].classList.contains("active")) { - els[i].classList.remove("active"); - }; - }; - - e.target.classList.add("active"); - - // Updates the layers' active status - for (let j = 0; j < this.__LAYERNAMES.length; j++) { - if(this.__LAYERNAMES[j].name !== e.target.id) { - mapgl.setLayoutProperty(this.__LAYERNAMES[j].name, 'visibility', 'none'); - }; - }; - - const filteredDown = this.__LAYERNAMES.filter(item => item.name === e.target.id) - - if(filteredDown.length > 0) { - mapgl.setLayoutProperty(e.target.id, 'visibility', 'visible'); - changeGranularity(filteredDown[0].groupingName) - } - - } - } - - function getMaxState(arr) { - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.state"].value)); - try { - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function getMaxCBSA(arr) { - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.cbsa"].value)); - try { - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function getMaxZip(arr) { - const set = data.filter(row => arr.hasOwnProperty(row["dim_zi_map_vis.zip"].value)); - try { - let max = set.reduce((max, item) => max[measureName].value > item[measureName].value ? max : item); - return max[measureName].value - } catch { - return 2 - } - } - - function determineNull(arr, prop) { - return arr.every(row => row[prop].value === null); - } - - function createStatesViz() { - console.log("CREATING STATES VIZ") - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - - const searchData = stateData.adm1.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['name']] = featureData - } - }) - return lookupData; - } - - console.log("GETTING MAX STATE") - const maxValue = getMaxState(lookupData) - - console.log("adding layer") - mapgl.addLayer({ - id: 'states-join', - type: 'fill', - source: 'statesData', - 'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - }, - 'source-layer': 'boundaries_admin_1', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - console.log("CREATING POPUP") - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - console.log("ADDING EVENT HANDLERS") - - mapgl.on('mousemove', 'states-join', (e) => { - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const state = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove; - return; - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'states-join', () => { - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'statesData', id: hoveredStateId, sourceLayer: 'boundaries_admin_1' }, - { hover: false } - ); - popup.remove(); - } - hoveredStateId = null; - }); - - mapgl.on('click', 'states-join', (e) => hostClickEvent(e, 'states-join', lookupData, localesWrapper)); - - function setStates() { - console.log("SETTING STATES") - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.state"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "statesData", - sourceLayer: 'boundaries_admin_1', - id: lookupData[row["dim_zi_map_vis.state"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - fipsCode: lookupData[row["dim_zi_map_vis.state"].value].unit_code, - name: lookupData[row["dim_zi_map_vis.state"].value].name, - hovered: false - } - ) - } - } - - createLegend(data, "dim_zi_map_vis.state", maxValue) - - // Check if `statesData` source is loaded. - function setAfterLoadStates(event) { - if (event.sourceID !== 'statesData' && !event.isSourceLoaded) return; - setStates(); - mapgl.off('sourcedata', setAfterLoadStates); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('statesData')) { - setStates(); - } else { - mapgl.on('sourcedata', setAfterLoadStates); - } - } - - - function createCBSAsViz() { - console.log("CREATING CBSA's VIZ") - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - - const searchData = cbsaData.sta2.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['name'].replace(",", "")] = featureData - } - }) - return lookupData; - } - - - - const maxValue = getMaxCBSA(lookupData) - - mapgl.addLayer({ - id: 'cbsas-join', - type: 'fill', - source: 'cbsaData', - 'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - }, - 'source-layer': 'boundaries_stats_2', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - - mapgl.on('mousemove', 'cbsas-join', (e) => { - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const cbsa = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'cbsaData', id: hoveredStateId, sourceLayer: 'boundaries_stats_2' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'cbsaData', id: hoveredStateId, sourceLayer: 'boundaries_stats_2' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'cbsas-join', () => { - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'cbsaData', id: hoveredStateId, sourceLayer: 'boundaries_stats_2' }, - { hover: false } - ); - popup.remove(); - } - hoveredStateId = null; - }); - - mapgl.on('click', 'cbsas-join', (e) => hostClickEvent(e, 'cbsas-join', lookupData, localesWrapper, true)); - - function setStates() { - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.cbsa"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "cbsaData", - sourceLayer: 'boundaries_stats_2', - id: lookupData[row["dim_zi_map_vis.cbsa"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.cbsa"].value].name, - hovered: false - } - ) - } - } - - createLegend(data, "dim_zi_map_vis.cbsa", maxValue) - - // Check if `statesData` source is loaded. - function setAfterLoadCBSAs(event) { - if (event.sourceID !== 'cbsaData' && !event.isSourceLoaded) return; - setStates(); - mapgl.off('sourcedata', setAfterLoadCBSAs); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('cbsaData')) { - setStates(); - } else { - mapgl.on('sourcedata', setAfterLoadCBSAs); - } - } - - function createZipsViz() { - console.log("CREATING zip's VIZ") - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - - const searchData = zipData.pos4.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['unit_code']] = featureData - } - }) - return lookupData; - } - - const maxValue = getMaxZip(lookupData) - - mapgl.addLayer({ - id: 'zips-join', - type: 'fill', - source: 'zipData', - 'layout': { - // Make the layer visible by default. - 'visibility': 'visible' - }, - 'source-layer': 'boundaries_postal_4', - paint: { - 'fill-color': [ - // In the case that 'feature-state': 'requestedKPI' is not null, interpolate the colors between the min and max, if it is null make the layer white. - 'case', - ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'], - 'rgba(255, 255, 255, 0)' - ] - } - }, 'waterway-label'); - - const popup = new mapboxgl.Popup({ - closeButton: false, - closeOnClick: false, - className: 'gtm-map-popup', - maxWidth: '300px' - }); - - - mapgl.on('mousemove', 'zips-join', (e) => { - if (e.features.length > 0) { - - const realCoords = [e.lngLat.lng, e.lngLat.lat] - const reqKPI = e.features[0].state.requestedKPI; - const zip = e.features[0].state.name - - const description = ` - ` - - if(e.features[0].state.requestedKPI) { - popup.setLngLat(realCoords).setHTML(description).addTo(mapgl); - } else { - popup.remove - } - - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'zipData', id: hoveredStateId, sourceLayer: 'boundaries_postal_4' }, - { hover: false } - ); - } - hoveredStateId = e.features[0].id; - mapgl.setFeatureState( - { source: 'zipData', id: hoveredStateId, sourceLayer: 'boundaries_postal_4' }, - { hover: true } - ); - } - }); - - // When the mouse leaves the state-fill layer, update the feature state of the - // previously hovered feature. - mapgl.on('mouseleave', 'zips-join', () => { - if (hoveredStateId !== null) { - mapgl.setFeatureState( - { source: 'zipData', id: hoveredStateId, sourceLayer: 'boundaries_postal_4' }, - { hover: false } - ); - popup.remove(); - } - hoveredStateId = null; - }); - - mapgl.on('click', 'zips-join', (e) => hostClickEvent(e, 'zips-join', lookupData, localesWrapper)); - - function setStates() { - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.zip"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "zipData", - sourceLayer: 'boundaries_postal_4', - id: lookupData[row["dim_zi_map_vis.zip"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.zip"].value].unit_code, - hovered: false - } - ) - } - } - - createLegend(data, "dim_zi_map_vis.zip", maxValue) - - // Check if `statesData` source is loaded. - function setAfterLoadZips(event) { - if (event.sourceID !== 'zipData' && !event.isSourceLoaded) return; - setStates(); - mapgl.off('sourcedata', setAfterLoadZips); - } - - // If `statesData` source is loaded, call `setStates()`. - if (mapgl.isSourceLoaded('zipData')) { - setStates(); - } else { - mapgl.on('sourcedata', setAfterLoadZips); - } - } - - mapgl.on('style.load', () => { - mapgl.setFog({ - 'color': 'rgb(186, 210, 235)', // Lower atmosphere - 'high-color': 'rgb(36, 92, 223)', // Upper atmosphere - 'horizon-blend': 0.02, // Atmosphere thickness (default 0.2 at low zooms) - 'space-color': 'rgb(11, 11, 25)', // Background color - 'star-intensity': 0.6 // Background star brightness (default 0.35 at low zoooms ) - }); // Set the default atmosphere style - }); - - const autozoomCBSA = () => { - console.log("AUTOZOOM CBSA") - if(mapgl.getSource('cbsaData') && mapgl.isSourceLoaded('cbsaData')) { - const fu = mapgl.queryRenderedFeatures({ layers: ['cbsas-join'] }) - //const fu = mapgl.querySourceFeatures('cbsaData', { sourceLayer: 'boundaries_stats_2' }) - const f = fu.filter((feature) => feature.state && feature.state.requestedKPI) - if (f.length > 0) { - var bb = bbox({ type: 'FeatureCollection', features: f }); - mapgl.fitBounds(bb, {padding: 20}); - mapgl.off("idle", autozoomCBSA) - } - } - } - - runVisUpdate() - - const runVisUpdate = () => { - //mapgl.on("idle", autozoomCBSA) - //mapgl.on("idle", autozoomZip) - - const updateStates = () => { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - const searchData = stateData.adm1.data.all; - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') lookupData[featureData['name']] = featureData - }) - return lookupData; - } - - mapgl.removeFeatureState({source: "statesData", sourceLayer: 'boundaries_admin_1'}) - - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.state"].value)) continue; - mapgl.setFeatureState( - { - source: "statesData", - sourceLayer: 'boundaries_admin_1', - id: lookupData[row["dim_zi_map_vis.state"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - fipsCode: lookupData[row["dim_zi_map_vis.state"].value].unit_code, - name: lookupData[row["dim_zi_map_vis.state"].value].name, - hovered: false - } - ) - } - - const maxValue = getMaxState(lookupData) - createLegend(data, "dim_zi_map_vis.state", maxValue) - - mapgl.setPaintProperty( - 'states-join', - 'fill-color', - ['case', ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'],'rgba(255, 255, 255, 0)'] - ) - } - - const updateCBSAs = () => { - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - const searchData = cbsaData.sta2.data.all - - Object.keys(searchData).forEach((key) => { - const featureData = searchData[key] - if(featureData.iso_3166_1 === 'US') lookupData[featureData['name'].replace(",", "")] = featureData - }) - return lookupData; - } - - mapgl.removeFeatureState({source: "cbsaData", sourceLayer: 'boundaries_stats_2'}) - - for (let i = 0; i < data.length; i++) { - const row = data[i] - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.cbsa"].value)) continue; - mapgl.setFeatureState( - { - source: "cbsaData", - sourceLayer: 'boundaries_stats_2', - id: lookupData[row["dim_zi_map_vis.cbsa"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.cbsa"].value].name, - hovered: false - } - ) - } - - const maxValue = getMaxCBSA(lookupData) - createLegend(data, "dim_zi_map_vis.cbsa", maxValue) - - mapgl.setPaintProperty( - 'cbsas-join', - 'fill-color', - ['case', ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'],'rgba(255, 255, 255, 0)'] - ) - } - - const updateZips = () => { - console.log("Updating Zips") - const lookupData = filterLookupTable(); - - function filterLookupTable() { - const lookupData = {}; - const searchData = zipData.pos4.data.all; - - Object.keys(searchData).forEach(function (key) { - const featureData = searchData[key]; - if(featureData.iso_3166_1 === 'US') { - lookupData[featureData['unit_code']] = featureData; - } - }) - return lookupData; - } - - mapgl.removeFeatureState({source: "zipData", sourceLayer: 'boundaries_postal_4'}); - - for (let i = 0; i < data.length; i++) { - const row = data[i]; - if(!lookupData.hasOwnProperty(row["dim_zi_map_vis.zip"].value)) { - continue; - } - mapgl.setFeatureState( - { - source: "zipData", - sourceLayer: 'boundaries_postal_4', - id: lookupData[row["dim_zi_map_vis.zip"].value].feature_id - }, - { - requestedKPI: row[measureName].value, - name: lookupData[row["dim_zi_map_vis.zip"].value].unit_code, - hovered: false - } - ) - } - - const maxValue = getMaxZip(lookupData); - - createLegend(data, "dim_zi_map_vis.zip", maxValue); - - mapgl.setPaintProperty( - 'zips-join', - 'fill-color', - ['case', ['!=', ['feature-state', 'requestedKPI'], null], ['interpolate', ['linear'], ['feature-state', 'requestedKPI'], 1, 'rgba(255,237,234,0.6)', maxValue, 'rgba(179,18,31,0.6)'],'rgba(255, 255, 255, 0)'] - ); - } - - //if(mapgl.getSource('statesData') && mapgl.isSourceLoaded('statesData')) updateStates(); - //if(mapgl.getSource('cbsaData') && mapgl.isSourceLoaded('cbsaData')) updateCBSAs(); - //if(mapgl.getSource('zipData') && mapgl.isSourceLoaded('zipData')) updateZips(); - } - - console.log("Update Async complete") - done() - } -}); \ No newline at end of file diff --git a/src/visualizations/custom/maps/mapbox-boundaries-sta1-v3_4.json b/src/visualizations/custom/maps/mapbox-boundaries-sta1-v3_4.json deleted file mode 100644 index b139a2b..0000000 --- a/src/visualizations/custom/maps/mapbox-boundaries-sta1-v3_4.json +++ /dev/null @@ -1,192 +0,0 @@ -{"sta1": {"type": "stats","level": 1,"PolyTilesetName": "mapbox.boundaries-sta1-v3","PolyLayerName": "boundaries_stats_1","PointTilesetName": "mapbox.boundaries-staPoints-v3","PointLayerName": "points_stats_1","data": { -"AR":{ -}, -"CN":{ -}, -"IN":{ -}, -"JP":{ -}, -"MA":{ -}, -"RU":{ -}, -"TR":{ -}, -"US":{ -}, -"all":{ -"USS100":{"feature_id":13033,"worldview":"all","unit_code":"000","name":"remainder","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-110.6813,38.885],"bounds":[-187.6522,18.8655,-66.8854,71.4398],"area_sqkm":14549637}, -"USS101":{"feature_id":8729321,"worldview":"all","unit_code":"444","name":"Pueblo-CaƱon City, CO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-104.5535,38.2185],"bounds":[-106.0128,37.7347,-104.0539,38.6976],"area_sqkm":13007}, -"USS102":{"feature_id":8794857,"worldview":"all","unit_code":"446","name":"Pullman-Moscow, WA-ID","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-117.1668,46.8403],"bounds":[-118.2494,46.4168,-116.3294,47.2607],"area_sqkm":12333}, -"USS104":{"feature_id":8860393,"worldview":"all","unit_code":"448","name":"Quincy-Hannibal, IL-MO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-91.3802,39.7896],"bounds":[-91.9527,39.3187,-90.912,40.2572],"area_sqkm":7817}, -"USS105":{"feature_id":8925929,"worldview":"all","unit_code":"450","name":"Raleigh-Durham-Cary, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.7309,35.9018],"bounds":[-79.5558,35.2547,-78.0066,36.5438],"area_sqkm":15287}, -"USS106":{"feature_id":8336105,"worldview":"all","unit_code":"428","name":"Philadelphia-Reading-Camden, PA-NJ-DE-MD","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-75.2459,39.7394],"bounds":[-76.4402,38.7887,-74.2321,40.6773],"area_sqkm":28681}, -"USS107":{"feature_id":8991465,"worldview":"all","unit_code":"452","name":"Rapid City-Spearfish, SD","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-103.0283,44.3668],"bounds":[-104.056,43.6851,-102,45.0397],"area_sqkm":25573}, -"USS108":{"feature_id":9057001,"worldview":"all","unit_code":"454","name":"Redding-Red Bluff, CA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-122.0867,40.4945],"bounds":[-123.0688,39.7974,-121.32,41.185],"area_sqkm":23213}, -"USS109":{"feature_id":8467177,"worldview":"all","unit_code":"430","name":"Pittsburgh-New Castle-Weirton, PA-OH-WV","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-79.9413,40.4507],"bounds":[-80.9419,39.7207,-78.799,41.1731],"area_sqkm":24325}, -"USS10A":{"feature_id":8598249,"worldview":"all","unit_code":"438","name":"Portland-Lewiston-South Portland, ME","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-70.3256,43.7073],"bounds":[-70.9891,42.9171,-69.6647,44.4872],"area_sqkm":12031}, -"USS10B":{"feature_id":8663785,"worldview":"all","unit_code":"440","name":"Portland-Vancouver-Salem, OR-WA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-122.5753,45.3049],"bounds":[-123.8168,44.1999,-121.5144,46.3886],"area_sqkm":47536}, -"USS10C":{"feature_id":9122537,"worldview":"all","unit_code":"456","name":"Reno-Carson City-Fernley, NV","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-119.6676,40.2245],"bounds":[-120.0065,38.414,-118.7537,41.9975],"area_sqkm":32962}, -"USS10D":{"feature_id":9188073,"worldview":"all","unit_code":"458","name":"Richmond-Connersville, IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.0579,39.7665],"bounds":[-85.3016,39.5255,-84.8108,40.0064],"area_sqkm":2091}, -"USS10E":{"feature_id":9253609,"worldview":"all","unit_code":"462","name":"Rochester-Austin, MN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-92.5623,43.9793],"bounds":[-93.0496,43.4995,-91.7302,44.4553],"area_sqkm":11551}, -"USS10F":{"feature_id":9319145,"worldview":"all","unit_code":"464","name":"Rochester-Batavia-Seneca Falls, NY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.5843,43.0748],"bounds":[-78.4687,42.4592,-76.586,43.6843],"area_sqkm":21779}, -"USS10G":{"feature_id":9384681,"worldview":"all","unit_code":"466","name":"Rockford-Freeport-Rochelle, IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.197,42.196],"bounds":[-89.9265,41.8855,-88.7054,42.5058],"area_sqkm":7458}, -"USS10H":{"feature_id":9450217,"worldview":"all","unit_code":"468","name":"Rocky Mount-Wilson-Roanoke Rapids, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.7181,36.0665],"bounds":[-78.2572,35.5834,-77.0662,36.5473],"area_sqkm":8686}, -"USS10K":{"feature_id":9515753,"worldview":"all","unit_code":"472","name":"Sacramento-Roseville, CA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-121.0975,38.8337],"bounds":[-122.422,38.0184,-119.8772,39.6395],"area_sqkm":25136}, -"USS10M":{"feature_id":9581289,"worldview":"all","unit_code":"474","name":"Saginaw-Midland-Bay City, MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.1536,43.563],"bounds":[-84.6081,43.1285,-83.6952,43.9971],"area_sqkm":7056}, -"USS10N":{"feature_id":9646825,"worldview":"all","unit_code":"476","name":"St. Louis-St. Charles-Farmington, MO-IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-90.0333,38.5887],"bounds":[-91.4186,37.641,-88.6952,39.5233],"area_sqkm":30323}, -"USS10P":{"feature_id":9777897,"worldview":"all","unit_code":"482","name":"Salt Lake City-Provo-Orem, UT","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-112.473,40.6703],"bounds":[-114.0479,39.3145,-110.0007,42.0015],"area_sqkm":86785}, -"USS10Q":{"feature_id":9908969,"worldview":"all","unit_code":"488","name":"San Jose-San Francisco-Oakland, CA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-121.4559,37.5428],"bounds":[-123.6325,36.1968,-120.0521,38.8642],"area_sqkm":49614}, -"USS10R":{"feature_id":10040041,"worldview":"all","unit_code":"496","name":"Savannah-Hinesville-Statesboro, GA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.3151,31.9927],"bounds":[-82.1479,31.3271,-80.783,32.6534],"area_sqkm":11977}, -"USS10S":{"feature_id":10171113,"worldview":"all","unit_code":"500","name":"Seattle-Tacoma, WA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-122.3811,47.5326],"bounds":[-123.5061,46.3835,-120.6861,48.657],"area_sqkm":51963}, -"USS10V":{"feature_id":10302185,"worldview":"all","unit_code":"515","name":"South Bend-Elkhart-Mishawaka, IN-MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.0723,41.6448],"bounds":[-87.2078,41.0419,-85.6512,42.2435],"area_sqkm":13987}, -"USS10W":{"feature_id":10433257,"worldview":"all","unit_code":"518","name":"Spokane-Spokane Valley-Coeur d'Alene, WA-ID","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-117.8636,48.1374],"bounds":[-118.4024,47.2593,-116.3294,49.0007],"area_sqkm":21711}, -"USS10Z":{"feature_id":10498793,"worldview":"all","unit_code":"522","name":"Springfield-Jacksonville-Lincoln, IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.8938,39.8375],"bounds":[-90.6461,39.3458,-89.024,40.3253],"area_sqkm":11305}, -"USS110":{"feature_id":10564329,"worldview":"all","unit_code":"524","name":"State College-DuBois, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.1607,40.9731],"bounds":[-78.8066,40.6916,-77.1438,41.2538],"area_sqkm":7781}, -"USS111":{"feature_id":10629865,"worldview":"all","unit_code":"525","name":"Steamboat Springs-Craig, CO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-107.8544,40.4637],"bounds":[-109.0512,39.9187,-106.6261,41.0034],"area_sqkm":24340}, -"USS112":{"feature_id":10695401,"worldview":"all","unit_code":"532","name":"Syracuse-Auburn, NY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-76.2954,43.1657],"bounds":[-76.7535,42.6186,-75.2395,43.7081],"area_sqkm":12937}, -"USS114":{"feature_id":10760937,"worldview":"all","unit_code":"534","name":"Toledo-Findlay-Tiffin, OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-83.3646,41.3905],"bounds":[-84.3996,40.8181,-82.6161,41.9594],"area_sqkm":12828}, -"USS115":{"feature_id":10826473,"worldview":"all","unit_code":"536","name":"Tucson-Nogales, AZ","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-111.7087,31.9254],"bounds":[-113.3341,31.3323,-110.4476,32.5142],"area_sqkm":31990}, -"USS116":{"feature_id":10892009,"worldview":"all","unit_code":"538","name":"Tulsa-Muskogee-Bartlesville, OK","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-96.0311,36.136],"bounds":[-97.0645,35.2616,-95.0499,36.9995],"area_sqkm":24869}, -"USS117":{"feature_id":11023081,"worldview":"all","unit_code":"540","name":"Tyler-Jacksonville, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-95.2095,32.0586],"bounds":[-95.5945,31.4257,-94.8659,32.687],"area_sqkm":6171}, -"USS118":{"feature_id":11088617,"worldview":"all","unit_code":"544","name":"Victoria-Port Lavaca, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-97.0221,28.5648],"bounds":[-97.7785,28.0228,-96.3227,29.104],"area_sqkm":8239}, -"USS119":{"feature_id":11154153,"worldview":"all","unit_code":"545","name":"Virginia Beach-Norfolk, VA-NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-76.313,36.3817],"bounds":[-77.5019,35.1406,-75.4001,37.6032],"area_sqkm":22685}, -"USS11B":{"feature_id":11219689,"worldview":"all","unit_code":"548","name":"Washington-Baltimore-Arlington, DC-MD-VA-WV-PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.344,39.0994],"bounds":[-78.9799,37.8885,-75.7478,40.2899],"area_sqkm":47402}, -"USS11C":{"feature_id":11285225,"worldview":"all","unit_code":"554","name":"Wausau-Stevens Point-Wisconsin Rapids, WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.7695,44.9035],"bounds":[-90.318,44.2434,-89.223,45.5559],"area_sqkm":15008}, -"USS11D":{"feature_id":11350761,"worldview":"all","unit_code":"556","name":"Wichita-Winfield, KS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-97.1667,37.5889],"bounds":[-97.8084,36.9985,-96.5228,38.1751],"area_sqkm":17382}, -"USS11E":{"feature_id":11416297,"worldview":"all","unit_code":"558","name":"Williamsport-Lock Haven, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.2608,41.2803],"bounds":[-78.0934,40.9629,-76.4476,41.5969],"area_sqkm":7388}, -"USS11F":{"feature_id":11481833,"worldview":"all","unit_code":"566","name":"Youngstown-Warren, OH-PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-80.7607,41.0441],"bounds":[-81.0874,40.5825,-79.9992,41.5017],"area_sqkm":7853}, -"USS11G":{"feature_id":2306793,"worldview":"all","unit_code":"200","name":"Columbus-West Point, MS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.4723,33.5467],"bounds":[-89.0361,33.2869,-88.2489,33.8124],"area_sqkm":2905}, -"USS11H":{"feature_id":4666089,"worldview":"all","unit_code":"297","name":"Jackson-Brownsville, TN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.0202,35.7366],"bounds":[-89.5021,35.2473,-88.3518,36.2228],"area_sqkm":7183}, -"USS11J":{"feature_id":5649129,"worldview":"all","unit_code":"324","name":"Lake Charles-Jennings, LA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-93.1885,30.0098],"bounds":[-93.9292,29.5271,-92.5831,30.4905],"area_sqkm":11078}, -"USS11M":{"feature_id":8270569,"worldview":"all","unit_code":"426","name":"Pensacola-Ferry Pass, FL-AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-87.1624,30.7472],"bounds":[-87.6349,30.2295,-86.6881,31.262],"area_sqkm":9086}, -"USS11N":{"feature_id":78569,"worldview":"all","unit_code":"104","name":"Albany-Schenectady, NY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-74.0161,42.8994],"bounds":[-74.7743,41.978,-73.2414,43.8085],"area_sqkm":22141}, -"USS11P":{"feature_id":144105,"worldview":"all","unit_code":"106","name":"Albuquerque-Santa Fe-Las Vegas, NM","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-105.5889,35.6401],"bounds":[-107.6265,34.2598,-103.637,37.0002],"area_sqkm":76303}, -"USS11Q":{"feature_id":275177,"worldview":"all","unit_code":"108","name":"Amarillo-Pampa-Borger, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-101.7904,35.4054],"bounds":[-103.0425,34.7474,-100.5381,36.058],"area_sqkm":25261}, -"USS11R":{"feature_id":799465,"worldview":"all","unit_code":"146","name":"Bloomsburg-Berwick-Sunbury, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-76.7226,40.9582],"bounds":[-77.3642,40.6035,-76.2078,41.3102],"area_sqkm":5999}, -"USS11S":{"feature_id":865001,"worldview":"all","unit_code":"147","name":"Boise City-Mountain Home-Ontario, ID-OR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-116.6572,43.267],"bounds":[-118.2339,41.9961,-114.9673,44.5108],"area_sqkm":89596}, -"USS11T":{"feature_id":930537,"worldview":"all","unit_code":"148","name":"Boston-Worcester-Providence, MA-RI-NH-CT","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-71.4529,42.4851],"bounds":[-72.3159,41.0958,-69.8589,43.761],"area_sqkm":43665}, -"USS11V":{"feature_id":340713,"worldview":"all","unit_code":"118","name":"Appleton-Oshkosh-Neenah, WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.5397,44.2422],"bounds":[-88.8868,43.8915,-88.0417,44.5906],"area_sqkm":5852}, -"USS11W":{"feature_id":406249,"worldview":"all","unit_code":"120","name":"Asheville-Marion-Brevard, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.5866,35.5483],"bounds":[-83.2574,35.0274,-81.8241,36.0659],"area_sqkm":9148}, -"USS11X":{"feature_id":471785,"worldview":"all","unit_code":"122","name":"Atlanta--Athens-Clarke County--Sandy Springs, GA-AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.1662,33.7649],"bounds":[-85.5937,32.69,-82.7795,34.8277],"area_sqkm":41553}, -"USS11Y":{"feature_id":537321,"worldview":"all","unit_code":"140","name":"Bend-Prineville, OR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-120.7157,44.0895],"bounds":[-122.0024,43.6111,-119.655,44.564],"area_sqkm":21744}, -"USS11Z":{"feature_id":602857,"worldview":"all","unit_code":"142","name":"Birmingham-Hoover-Talladega, AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.5961,33.4912],"bounds":[-87.6361,32.6601,-85.7946,34.3138],"area_sqkm":21458}, -"USS120":{"feature_id":668393,"worldview":"all","unit_code":"144","name":"Bloomington-Bedford, IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.5259,39.081],"bounds":[-87.0549,38.6866,-86.2753,39.4733],"area_sqkm":4181}, -"USS121":{"feature_id":733929,"worldview":"all","unit_code":"145","name":"Bloomington-Pontiac, IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.6117,40.6992],"bounds":[-89.2694,40.2809,-88.235,41.1142],"area_sqkm":7629}, -"USS122":{"feature_id":996073,"worldview":"all","unit_code":"150","name":"Bowling Green-Glasgow, KY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.1104,37.0133],"bounds":[-86.9449,36.6285,-85.4418,37.3951],"area_sqkm":7897}, -"USS123":{"feature_id":1061609,"worldview":"all","unit_code":"154","name":"Brownsville-Harlingen-Raymondville, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-97.4915,26.2238],"bounds":[-98.0042,25.8372,-97.0893,26.6118],"area_sqkm":5980}, -"USS124":{"feature_id":1127145,"worldview":"all","unit_code":"160","name":"Buffalo-Cheektowaga-Olean, NY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.7332,42.8179],"bounds":[-79.3121,41.9976,-78.3081,43.6348],"area_sqkm":13008}, -"USS126":{"feature_id":1389289,"worldview":"all","unit_code":"164","name":"Cape Girardeau-Sikeston, MO-IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.7373,37.2341],"bounds":[-90.2232,36.8595,-89.1329,37.6065],"area_sqkm":6151}, -"USS127":{"feature_id":1454825,"worldview":"all","unit_code":"168","name":"Cedar Rapids-Iowa City, IA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-91.5995,41.7334],"bounds":[-92.2993,41.1615,-90.898,42.2991],"area_sqkm":11214}, -"USS128":{"feature_id":1520361,"worldview":"all","unit_code":"170","name":"Charleston-Huntington-Ashland, WV-OH-KY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.117,38.3715],"bounds":[-83.3415,37.6406,-80.8174,39.0954],"area_sqkm":23840}, -"USS129":{"feature_id":1585897,"worldview":"all","unit_code":"172","name":"Charlotte-Concord, NC-SC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-80.9196,35.2628],"bounds":[-81.7681,34.4578,-79.8485,36.0588],"area_sqkm":20903}, -"USS12A":{"feature_id":1651433,"worldview":"all","unit_code":"174","name":"Chattanooga-Cleveland-Dalton, TN-GA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.0881,35.0594],"bounds":[-85.873,34.2861,-84.2924,35.8252],"area_sqkm":15813}, -"USS12B":{"feature_id":1716969,"worldview":"all","unit_code":"176","name":"Chicago-Naperville, IL-IN-WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-87.7326,41.7103],"bounds":[-89.8624,40.7365,-86.4863,42.6699],"area_sqkm":45358}, -"USS12C":{"feature_id":1782505,"worldview":"all","unit_code":"178","name":"Cincinnati-Wilmington-Maysville, OH-KY-IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.4869,39.0955],"bounds":[-85.2986,38.4563,-83.5763,39.7293],"area_sqkm":17680}, -"USS12D":{"feature_id":1848041,"worldview":"all","unit_code":"184","name":"Cleveland-Akron-Canton, OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.922,41.2792],"bounds":[-82.9529,40.2143,-80.5192,42.3271],"area_sqkm":32854}, -"USS12E":{"feature_id":1913577,"worldview":"all","unit_code":"185","name":"Cleveland-Indianola, MS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-90.7452,33.6963],"bounds":[-91.2326,33.27,-90.4503,34.1208],"area_sqkm":5035}, -"USS12F":{"feature_id":1979113,"worldview":"all","unit_code":"188","name":"Clovis-Portales, NM","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-103.4964,34.2651],"bounds":[-103.9492,33.5701,-103.0427,34.9542],"area_sqkm":12121}, -"USS12G":{"feature_id":2044649,"worldview":"all","unit_code":"190","name":"Columbia-Moberly-Mexico, MO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-92.2829,39.1281],"bounds":[-93.06,38.6432,-91.409,39.6104],"area_sqkm":9747}, -"USS12H":{"feature_id":2110185,"worldview":"all","unit_code":"192","name":"Columbia-Orangeburg-Newberry, SC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.2491,33.8989],"bounds":[-82.0103,33.1769,-80.2215,34.6148],"area_sqkm":17583}, -"USS12J":{"feature_id":2175721,"worldview":"all","unit_code":"194","name":"Columbus-Auburn-Opelika, GA-AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.913,32.4033],"bounds":[-85.697,31.9203,-84.2862,32.8836],"area_sqkm":10575}, -"USS12K":{"feature_id":2241257,"worldview":"all","unit_code":"198","name":"Columbus-Marion-Zanesville, OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.4586,39.9452],"bounds":[-84.0148,39.1678,-81.2259,40.7127],"area_sqkm":28958}, -"USS12M":{"feature_id":2372329,"worldview":"all","unit_code":"204","name":"Corpus Christi-Kingsville-Alice, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-98.0088,27.4599],"bounds":[-98.8033,26.5979,-96.7904,28.3192],"area_sqkm":23772}, -"USS12N":{"feature_id":2437865,"worldview":"all","unit_code":"206","name":"Dallas-Fort Worth, TX-OK","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-97.1449,32.9868],"bounds":[-98.5763,31.7962,-95.4281,34.1574],"area_sqkm":50064}, -"USS12P":{"feature_id":2503401,"worldview":"all","unit_code":"209","name":"Davenport-Moline, IA-IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-90.6138,41.5506],"bounds":[-91.3693,41.0636,-89.8566,42.0336],"area_sqkm":12016}, -"USS12Q":{"feature_id":2568937,"worldview":"all","unit_code":"212","name":"Dayton-Springfield-Kettering, OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.1629,40.0167],"bounds":[-84.8124,39.5503,-83.4945,40.4832],"area_sqkm":10605}, -"USS12R":{"feature_id":2634473,"worldview":"all","unit_code":"216","name":"Denver-Aurora, CO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-104.6999,39.857],"bounds":[-106.2102,38.6931,-103.5732,41.0019],"area_sqkm":44411}, -"USS12S":{"feature_id":2700009,"worldview":"all","unit_code":"217","name":"DeRidder-Fort Polk South, LA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-93.199,30.8848],"bounds":[-93.7417,30.4021,-92.8236,31.3648],"area_sqkm":7592}, -"USS12T":{"feature_id":2765545,"worldview":"all","unit_code":"218","name":"Des Moines-Ames-West Des Moines, IA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-93.7553,41.6856],"bounds":[-94.7449,41.1565,-92.4101,42.21],"area_sqkm":20560}, -"USS12V":{"feature_id":2831081,"worldview":"all","unit_code":"220","name":"Detroit-Warren-Ann Arbor, MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-83.4104,42.5215],"bounds":[-84.3633,41.7066,-82.3341,43.327],"area_sqkm":24797}, -"USS12W":{"feature_id":2896617,"worldview":"all","unit_code":"221","name":"Dixon-Sterling, IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.5932,41.7572],"bounds":[-90.2479,41.5837,-88.9387,41.9313],"area_sqkm":4950}, -"USS12X":{"feature_id":2962153,"worldview":"all","unit_code":"222","name":"Dothan-Ozark, AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.4363,31.384],"bounds":[-86.1935,30.9929,-84.9994,31.7744],"area_sqkm":6961}, -"USS12Y":{"feature_id":3027689,"worldview":"all","unit_code":"232","name":"Eau Claire-Menomonie, WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-91.529,44.9452],"bounds":[-92.1571,44.5959,-90.9215,45.2922],"area_sqkm":9331}, -"USS12Z":{"feature_id":3093225,"worldview":"all","unit_code":"233","name":"Edwards-Glenwood Springs, CO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-107.652,39.5367],"bounds":[-109.0515,38.9777,-106.1759,40.0915],"area_sqkm":18903}, -"USS130":{"feature_id":3158761,"worldview":"all","unit_code":"236","name":"Elmira-Corning, NY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.4184,42.2901],"bounds":[-77.7499,41.9986,-76.5358,42.5804],"area_sqkm":6348}, -"USS131":{"feature_id":3224297,"worldview":"all","unit_code":"238","name":"El Paso-Las Cruces, TX-NM","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-106.1074,31.8494],"bounds":[-107.2997,30.6293,-104.9071,33.0528],"area_sqkm":28759}, -"USS132":{"feature_id":3289833,"worldview":"all","unit_code":"240","name":"Erie-Meadville, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-80.1406,42.0093],"bounds":[-80.5199,41.4804,-79.6106,42.5161],"area_sqkm":9036}, -"USS133":{"feature_id":3355369,"worldview":"all","unit_code":"244","name":"Fargo-Wahpeton, ND-MN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-96.7804,46.59],"bounds":[-97.7062,45.9352,-96.1723,47.24],"area_sqkm":18942}, -"USS134":{"feature_id":3420905,"worldview":"all","unit_code":"246","name":"Fayetteville-Sanford-Lumberton, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-79.0999,34.9666],"bounds":[-79.768,34.2993,-78.4947,35.6283],"area_sqkm":12337}, -"USS136":{"feature_id":3486441,"worldview":"all","unit_code":"258","name":"Fort Wayne-Huntington-Auburn, IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.2449,41.1665],"bounds":[-85.6866,40.5682,-84.8021,41.7602],"area_sqkm":9744}, -"USS137":{"feature_id":3551977,"worldview":"all","unit_code":"260","name":"Fresno-Madera-Hanford, CA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-119.6171,36.7896],"bounds":[-120.9187,35.7887,-118.3606,37.778],"area_sqkm":30946}, -"USS138":{"feature_id":3617513,"worldview":"all","unit_code":"264","name":"Gainesville-Lake City, FL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.5103,29.7597],"bounds":[-83.2399,28.9145,-82.0491,30.5977],"area_sqkm":10569}, -"USS139":{"feature_id":3683049,"worldview":"all","unit_code":"266","name":"Grand Rapids-Kentwood-Muskegon, MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.9724,43.1201],"bounds":[-87.1472,42.4154,-84.8353,43.8155],"area_sqkm":27141}, -"USS13A":{"feature_id":3748585,"worldview":"all","unit_code":"267","name":"Green Bay-Shawano, WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.4933,44.8142],"bounds":[-89.2244,44.2404,-86.7784,45.3789],"area_sqkm":15019}, -"USS13B":{"feature_id":3814121,"worldview":"all","unit_code":"268","name":"Greensboro--Winston-Salem--High Point, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-79.9782,36.0343],"bounds":[-80.9736,35.5042,-79.2366,36.5623],"area_sqkm":16174}, -"USS13C":{"feature_id":3945193,"worldview":"all","unit_code":"273","name":"Greenville-Spartanburg-Anderson, SC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.2889,34.5867],"bounds":[-83.3539,33.9532,-81.3666,35.2155],"area_sqkm":17892}, -"USS13D":{"feature_id":3879657,"worldview":"all","unit_code":"272","name":"Greenville-Kinston-Washington, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.0222,35.4216],"bounds":[-77.8342,35.0081,-76.4678,35.8331],"area_sqkm":6441}, -"USS13E":{"feature_id":4010729,"worldview":"all","unit_code":"276","name":"Harrisburg-York-Lebanon, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-77.1031,40.1912],"bounds":[-77.6718,39.7198,-76.1512,40.6596],"area_sqkm":11729}, -"USS13F":{"feature_id":5845737,"worldview":"all","unit_code":"338","name":"Lima-Van Wert-Celina, OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.3412,40.672],"bounds":[-84.8042,40.3517,-83.8798,40.9903],"area_sqkm":5786}, -"USS13G":{"feature_id":4076265,"worldview":"all","unit_code":"277","name":"Harrisonburg-Staunton, VA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.9336,38.3674],"bounds":[-79.5333,37.8816,-78.4857,38.8501],"area_sqkm":6203}, -"USS13H":{"feature_id":4141801,"worldview":"all","unit_code":"278","name":"Hartford-East Hartford, CT","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-72.3613,41.6099],"bounds":[-73.0295,41.1778,-71.7877,42.0388],"area_sqkm":8253}, -"USS13K":{"feature_id":4272873,"worldview":"all","unit_code":"284","name":"Hot Springs-Malvern, AR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-93.0458,34.4621],"bounds":[-93.4081,34.1496,-92.6687,34.7735],"area_sqkm":4272}, -"USS13M":{"feature_id":5911273,"worldview":"all","unit_code":"339","name":"Lincoln-Beatrice, NE","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-96.6881,40.527],"bounds":[-97.3684,40.0008,-96.4633,41.0469],"area_sqkm":7804}, -"USS13N":{"feature_id":4338409,"worldview":"all","unit_code":"288","name":"Houston-The Woodlands, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-95.3084,29.6785],"bounds":[-96.7946,28.2797,-94.3534,31.0581],"area_sqkm":40609}, -"USS13P":{"feature_id":4403945,"worldview":"all","unit_code":"290","name":"Huntsville-Decatur, AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.9031,34.6498],"bounds":[-87.531,34.2991,-86.2558,34.999],"area_sqkm":8633}, -"USS13Q":{"feature_id":4469481,"worldview":"all","unit_code":"292","name":"Idaho Falls-Rexburg-Blackfoot, ID","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-112.3856,43.8165],"bounds":[-113.7984,42.863,-111.0439,44.755],"area_sqkm":34774}, -"USS13R":{"feature_id":4535017,"worldview":"all","unit_code":"294","name":"Indianapolis-Carmel-Muncie, IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.3225,39.5575],"bounds":[-87.0926,38.725,-85.2003,40.3798],"area_sqkm":24665}, -"USS13S":{"feature_id":4600553,"worldview":"all","unit_code":"296","name":"Ithaca-Cortland, NY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-76.2843,42.5298],"bounds":[-76.6967,42.263,-75.864,42.791],"area_sqkm":3491}, -"USS13T":{"feature_id":4731625,"worldview":"all","unit_code":"298","name":"Jackson-Vicksburg-Brookhaven, MS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-90.3052,32.3679],"bounds":[-91.1746,31.3492,-89.653,33.3751],"area_sqkm":20598}, -"USS13V":{"feature_id":4797161,"worldview":"all","unit_code":"300","name":"Jacksonville-St. Marys-Palatka, FL-GA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.8887,30.2516],"bounds":[-82.4598,29.3244,-81.1508,31.1696],"area_sqkm":15962}, -"USS13W":{"feature_id":4862697,"worldview":"all","unit_code":"304","name":"Johnson City-Kingsport-Bristol, TN-VA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.5929,36.443],"bounds":[-83.2889,35.9539,-81.6054,36.929],"area_sqkm":9406}, -"USS13X":{"feature_id":4928233,"worldview":"all","unit_code":"306","name":"Johnstown-Somerset, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.8822,40.2258],"bounds":[-79.4176,39.7216,-78.3484,40.726],"area_sqkm":6018}, -"USS13Y":{"feature_id":4993769,"worldview":"all","unit_code":"308","name":"Jonesboro-Paragould, AR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-90.6602,35.8538],"bounds":[-91.0392,35.4386,-90.1902,36.268],"area_sqkm":6577}, -"USS13Z":{"feature_id":5059305,"worldview":"all","unit_code":"309","name":"Joplin-Miami, MO-OK","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-94.339,37.0166],"bounds":[-95.0377,36.6675,-94.0523,37.3642],"area_sqkm":5692}, -"USS140":{"feature_id":5124841,"worldview":"all","unit_code":"310","name":"Kalamazoo-Battle Creek-Portage, MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.2366,42.092],"bounds":[-85.7918,41.7589,-84.7096,42.4222],"area_sqkm":8172}, -"USS141":{"feature_id":5190377,"worldview":"all","unit_code":"312","name":"Kansas City-Overland Park-Kansas City, MO-KS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-94.3381,39.0859],"bounds":[-95.5704,38.026,-93.4772,40.1301],"area_sqkm":37958}, -"USS143":{"feature_id":5452521,"worldview":"all","unit_code":"316","name":"Kokomo-Peru, IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.0533,40.6878],"bounds":[-86.3758,40.3736,-85.8621,40.9992],"area_sqkm":2292}, -"USS144":{"feature_id":5518057,"worldview":"all","unit_code":"318","name":"Lafayette-Opelousas-Morgan City, LA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-91.9936,30.048],"bounds":[-92.7386,29.2396,-91.0826,30.85],"area_sqkm":19117}, -"USS145":{"feature_id":5583593,"worldview":"all","unit_code":"320","name":"Lafayette-West Lafayette-Frankfort, IN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.9515,40.4331],"bounds":[-87.5314,40.1273,-86.2423,40.7376],"area_sqkm":7006}, -"USS147":{"feature_id":5714665,"worldview":"all","unit_code":"332","name":"Las Vegas-Henderson, NV","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-116.531,37.1181],"bounds":[-118.1963,35.0019,-114.0428,39.1634],"area_sqkm":86342}, -"USS148":{"feature_id":5780201,"worldview":"all","unit_code":"336","name":"Lexington-Fayette--Richmond--Frankfort, KY","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.2813,38.0056],"bounds":[-85.1701,37.5153,-83.4339,38.4929],"area_sqkm":10826}, -"USS149":{"feature_id":5976809,"worldview":"all","unit_code":"340","name":"Little Rock-North Little Rock, AR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-92.2573,34.6243],"bounds":[-93.3059,33.7042,-91.3453,35.5342],"area_sqkm":23120}, -"USS14B":{"feature_id":6042345,"worldview":"all","unit_code":"348","name":"Los Angeles-Long Beach, CA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-116.9325,34.581],"bounds":[-119.6363,32.75,-114.1312,35.8092],"area_sqkm":111084}, -"USS14C":{"feature_id":6107881,"worldview":"all","unit_code":"350","name":"Louisville","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.6779,38.1303],"bounds":[-86.4909,37.422,-84.867,38.8318],"area_sqkm":16913}, -"USS14D":{"feature_id":6173417,"worldview":"all","unit_code":"352","name":"Lubbock-Plainview-Levelland, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-101.8233,33.6396],"bounds":[-102.6154,32.9597,-101.0388,34.3131],"area_sqkm":14373}, -"USS14E":{"feature_id":6238953,"worldview":"all","unit_code":"356","name":"Macon-Bibb County--Warner Robins, GA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-83.7479,32.7436],"bounds":[-84.2026,32.2826,-83.2265,33.2026],"area_sqkm":7010}, -"USS14F":{"feature_id":6304489,"worldview":"all","unit_code":"357","name":"Madison-Janesville-Beloit, WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.7202,43.0704],"bounds":[-90.43,42.4919,-88.7763,43.6437],"area_sqkm":17605}, -"USS14G":{"feature_id":7680745,"worldview":"all","unit_code":"404","name":"New Bern-Morehead City, NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-76.8097,34.9757],"bounds":[-77.731,34.5317,-76.0066,35.4184],"area_sqkm":9951}, -"USS14H":{"feature_id":7746281,"worldview":"all","unit_code":"406","name":"New Orleans-Metairie-Hammond, LA-MS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.8429,29.9401],"bounds":[-90.9637,28.8551,-88.7584,31.016],"area_sqkm":30686}, -"USS14K":{"feature_id":6370025,"worldview":"all","unit_code":"359","name":"Mankato-New Ulm, MN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-94.4345,44.1742],"bounds":[-95.1088,43.8478,-93.7677,44.4982],"area_sqkm":6687}, -"USS14M":{"feature_id":6435561,"worldview":"all","unit_code":"360","name":"Mansfield-Ashland-Bucyrus, OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.6196,40.8084],"bounds":[-83.1127,40.5499,-82.125,41.066],"area_sqkm":4555}, -"USS14N":{"feature_id":7811817,"worldview":"all","unit_code":"408","name":"New York-Newark, NY-NJ-CT-PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-73.5695,40.8399],"bounds":[-75.6496,39.4752,-71.7775,42.177],"area_sqkm":53581}, -"USS14P":{"feature_id":6566633,"worldview":"all","unit_code":"362","name":"Martin-Union City, TN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.9713,36.2849],"bounds":[-89.4858,36.0618,-88.5163,36.5072],"area_sqkm":3665}, -"USS14Q":{"feature_id":6697705,"worldview":"all","unit_code":"365","name":"McAllen-Edinburg, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-98.4457,26.4116],"bounds":[-99.1718,26.0363,-97.8617,26.7857],"area_sqkm":8172}, -"USS14R":{"feature_id":6763241,"worldview":"all","unit_code":"366","name":"Medford-Grants Pass, OR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-123.0956,42.4979],"bounds":[-124.042,41.9951,-122.2827,42.9968],"area_sqkm":15569}, -"USS14S":{"feature_id":6828777,"worldview":"all","unit_code":"368","name":"Memphis-Forrest City, TN-MS-AR","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-90.1206,35.0401],"bounds":[-91.1525,34.4238,-89.1839,35.6523],"area_sqkm":16931}, -"USS14T":{"feature_id":6894313,"worldview":"all","unit_code":"370","name":"Miami-Port St. Lucie-Fort Lauderdale, FL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-80.4557,26.1424],"bounds":[-82.9875,24.3963,-79.9743,27.8614],"area_sqkm":34501}, -"USS14V":{"feature_id":6959849,"worldview":"all","unit_code":"372","name":"Midland-Odessa, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-101.9531,32.0888],"bounds":[-102.7991,31.6511,-101.6887,32.5253],"area_sqkm":8325}, -"USS14W":{"feature_id":7025385,"worldview":"all","unit_code":"376","name":"Milwaukee-Racine-Waukesha, WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.056,43.0657],"bounds":[-89.0136,42.4919,-87.0439,43.6335],"area_sqkm":21944}, -"USS14X":{"feature_id":7090921,"worldview":"all","unit_code":"378","name":"Minneapolis-St. Paul, MN-WI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-93.1962,45.06],"bounds":[-95.1399,43.8481,-92.1348,46.2471],"area_sqkm":42362}, -"USS14Y":{"feature_id":7156457,"worldview":"all","unit_code":"380","name":"Mobile-Daphne-Fairhope, AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.0146,30.9249],"bounds":[-88.4644,30.1444,-87.3666,31.6993],"area_sqkm":14392}, -"USS150":{"feature_id":7221993,"worldview":"all","unit_code":"384","name":"Monroe-Ruston, LA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-92.2571,32.6373],"bounds":[-92.8814,32.2586,-91.4334,33.0145],"area_sqkm":8698}, -"USS151":{"feature_id":7353065,"worldview":"all","unit_code":"390","name":"Morgantown-Fairmont, WV","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-79.9572,39.4587],"bounds":[-80.4982,39.1949,-79.4766,39.7214],"area_sqkm":4471}, -"USS152":{"feature_id":7418601,"worldview":"all","unit_code":"393","name":"Moses Lake-Othello, WA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-119.4919,47.296],"bounds":[-120.0429,46.6255,-117.9593,47.9622],"area_sqkm":17899}, -"USS153":{"feature_id":7484137,"worldview":"all","unit_code":"394","name":"Mount Pleasant-Alma, MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-84.8463,43.4678],"bounds":[-85.0888,43.1176,-84.3678,43.8154],"area_sqkm":4103}, -"USS154":{"feature_id":7549673,"worldview":"all","unit_code":"396","name":"Myrtle Beach-Conway, SC-NC","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.9796,33.7133],"bounds":[-79.6809,33.0498,-77.897,34.3718],"area_sqkm":10443}, -"USS155":{"feature_id":7615209,"worldview":"all","unit_code":"400","name":"Nashville-Davidson--Murfreesboro, TN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.558,35.8302],"bounds":[-87.6119,34.9994,-85.7786,36.6524],"area_sqkm":23242}, -"USS156":{"feature_id":7877353,"worldview":"all","unit_code":"412","name":"North Port-Sarasota, FL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-82.0651,27.2083],"bounds":[-82.823,26.7696,-81.5623,27.6467],"area_sqkm":9105}, -"USS157":{"feature_id":7942889,"worldview":"all","unit_code":"416","name":"Oklahoma City-Shawnee, OK","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-97.4652,35.4257],"bounds":[-98.3135,34.6812,-96.6193,36.1648],"area_sqkm":20297}, -"USS158":{"feature_id":8008425,"worldview":"all","unit_code":"420","name":"Omaha-Council Bluffs-Fremont, NE-IA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-96.0316,41.3271],"bounds":[-96.9091,40.7835,-95.1546,41.8663],"area_sqkm":17085}, -"USS159":{"feature_id":8073961,"worldview":"all","unit_code":"422","name":"Orlando-Lakeland-Deltona, FL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.4769,28.5103],"bounds":[-82.3117,27.3367,-80.6635,29.6711],"area_sqkm":27313}, -"USS15A":{"feature_id":8139497,"worldview":"all","unit_code":"424","name":"Paducah-Mayfield, KY-IL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.7925,36.9652],"bounds":[-89.1826,36.5012,-88.1872,37.4254],"area_sqkm":5470}, -"USS15B":{"feature_id":8205033,"worldview":"all","unit_code":"425","name":"Parkersburg-Marietta-Vienna, WV-OH","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.519,39.2718],"bounds":[-81.8537,38.8956,-81.0368,39.646],"area_sqkm":4196}, -"USS160":{"feature_id":8401641,"worldview":"all","unit_code":"429","name":"Phoenix-Mesa, AZ","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-111.6678,33.504],"bounds":[-113.3351,32.5013,-110.0006,34.4993],"area_sqkm":60180}, -"USS161":{"feature_id":10367721,"worldview":"all","unit_code":"517","name":"Spencer-Spirit Lake, IA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-95.1509,43.2062],"bounds":[-95.3888,42.9097,-94.9134,43.5012],"area_sqkm":3471}, -"USS162":{"feature_id":10236649,"worldview":"all","unit_code":"508","name":"Shreveport-Bossier City-Minden, LA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-93.6401,32.4342],"bounds":[-94.0434,31.8442,-93.1464,33.0195],"area_sqkm":10210}, -"USS163":{"feature_id":209641,"worldview":"all","unit_code":"107","name":"Altoona-Huntingdon, PA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-78.2355,40.4034],"bounds":[-78.6204,40.0614,-77.6766,40.7437],"area_sqkm":4827}, -"USS164":{"feature_id":10105577,"worldview":"all","unit_code":"497","name":"Scottsboro-Fort Payne, AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-85.8384,34.5939],"bounds":[-86.359,34.1993,-85.5136,34.9911],"area_sqkm":6017}, -"USS165":{"feature_id":1192681,"worldview":"all","unit_code":"161","name":"Burlington-Fort Madison-Keokuk, IA-IL-MO","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-91.3113,40.6347],"bounds":[-91.9508,40.1931,-90.7852,41.0742],"area_sqkm":9180}, -"USS166":{"feature_id":6501097,"worldview":"all","unit_code":"361","name":"Marinette-Iron Mountain, WI-MI","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-87.7772,45.5116],"bounds":[-88.6842,44.7644,-87.1278,46.247],"area_sqkm":15425}, -"USS167":{"feature_id":10957545,"worldview":"all","unit_code":"539","name":"Tupelo-Corinth, MS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-88.5333,34.5364],"bounds":[-89.2465,34.0739,-88.1563,34.9961],"area_sqkm":7276}, -"USS168":{"feature_id":7287529,"worldview":"all","unit_code":"388","name":"Montgomery-Selma-Alexander City, AL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-86.4426,32.5361],"bounds":[-87.4731,31.9616,-85.593,33.1073],"area_sqkm":16048}, -"USS169":{"feature_id":9843433,"worldview":"all","unit_code":"484","name":"San Antonio-New Braunfels-Pearsall, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-98.6257,29.3788],"bounds":[-99.6033,28.6127,-97.631,30.139],"area_sqkm":25357}, -"USS170":{"feature_id":4207337,"worldview":"all","unit_code":"279","name":"Hattiesburg-Laurel, MS","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-89.3115,31.569],"bounds":[-89.7558,30.9098,-88.8343,32.2244],"area_sqkm":10389}, -"USS171":{"feature_id":5255913,"worldview":"all","unit_code":"313","name":"Kennewick-Richland-Walla Walla, WA","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-118.995,46.2893],"bounds":[-119.8767,45.8362,-117.9915,46.7391],"area_sqkm":16221}, -"USS172":{"feature_id":9712361,"worldview":"all","unit_code":"480","name":"Salisbury-Cambridge, MD-DE","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-75.6835,38.4439],"bounds":[-76.4388,37.8866,-74.9842,39.0027],"area_sqkm":12853}, -"USS173":{"feature_id":1323753,"worldview":"all","unit_code":"163","name":"Cape Coral-Fort Myers-Naples, FL","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-81.406,26.365],"bounds":[-82.335,25.7695,-80.8727,26.9589],"area_sqkm":13656}, -"USS174":{"feature_id":1258217,"worldview":"all","unit_code":"162","name":"Burlington-South Burlington-Barre, VT","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-73.0755,44.5179],"bounds":[-73.3902,44.0129,-72.2171,45.0167],"area_sqkm":8007}, -"USS175":{"feature_id":5386985,"worldview":"all","unit_code":"315","name":"Knoxville-Morristown-Sevierville, TN","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-83.8751,36.0283],"bounds":[-84.9142,35.4605,-82.8961,36.592],"area_sqkm":16619}, -"USS176":{"feature_id":5321449,"worldview":"all","unit_code":"314","name":"Kerrville-Fredericksburg, TX","description":"core statistical area","unit_code_description":"","source_date":"2019","iso_3166_1_alpha_3":"USA","iso_3166_1":"US","z_min":1,"centroid":[-99.1724,30.1403],"bounds":[-99.7576,29.7814,-98.5878,30.4999],"area_sqkm":6523}} -}}} diff --git a/webpack.config.js b/webpack.config.js index ff8f4ea..633d668 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -17,7 +17,6 @@ var webpackConfig = { line: './src/visualizations/recycleable/line_charts/line.js', custom_pbg_table: './src/visualizations/custom/tables/custom_pbg_table.js', map_test: './src/visualizations/custom/maps/map_test.js', - map_min: './src/visualizations/custom/maps/map_minimal.js', }, output: { filename: "[name].js",