1import { 2 calculateIndicatorValue, 3 extractVariables, 4 getAggregationNorm, 5 parseIndicator 6} from "services/indicator_helpers" 7import { classifyValue } from "services/bin_classifier" 8 9function sourceNumber(value) { 10 if (value === null || value === undefined || value === "") return null 11 const number = Number(value) 12 return Number.isFinite(number) && number >= 0 ? number : null 13} 14 15function rounded(value) { 16 return Number.isFinite(value) ? Math.round(value * 1_000_000) / 1_000_000 : null 17} 18 19function propertyKey(variable) { 20 const clean = variable.replace(/^\(|\)$/g, '') 21 if (clean === "POPULATION") return "P20" 22 if (["LAND_AREA", "ALAND20_SQMI", "ALAND20_ACRES"].includes(clean)) return "ALAND20" 23 return clean 24} 25 26export function mapTileIndicatorMeta(mapConfig, renderMeta) { 27 const parsed = parseIndicator(mapConfig?.category_indicator, mapConfig?.scope || {}) 28 return { 29 global_breaks: renderMeta?.global_breaks || { category: [], partition: [] }, 30 p99_height: renderMeta?.p99_height || 1, 31 value_normalize_var: parsed?.normalize || null 32 } 33} 34 35// Builds the common in-memory state for maps whose values come from MVT 36// properties. Standard maps and embedded map layers must use the same saved 37// breaks, feature adapter, and legend inputs. 38export function createMapTileViewState(mapConfig, renderMeta, cache = {}) { 39 const indicatorMeta = mapTileIndicatorMeta(mapConfig, renderMeta) 40 return { 41 geoidToFeature: cache, 42 tilePropertyAdapter: createMapTileFeatureAdapter(mapConfig, renderMeta, cache), 43 precomputed: { views: { indicator: indicatorMeta } }, 44 globalBreaks: indicatorMeta.global_breaks, 45 precomputedP99Height: indicatorMeta.p99_height, 46 categoryBreaks: indicatorMeta.global_breaks.category || [], 47 partitionBreaks: [], 48 categoryLegendInfo: (mapConfig?.categories || []).map(() => ({ min: null, max: null })), 49 partitionLegendInfo: [] 50 } 51} 52 53// Adapts Census properties embedded in an MVT feature to the standard map 54// feature shape. The adapter uses the saved tract scale for every geography. 55export function createMapTileFeatureAdapter(mapConfig, renderMeta, cache = {}) { 56 const config = mapConfig || {} 57 const parsed = parseIndicator(config.category_indicator, config.scope || {}) 58 const variables = new Set() 59 extractVariables(parsed, variables) 60 const breaks = renderMeta?.global_breaks?.category || [] 61 62 return rawProperties => { 63 const geoid = rawProperties?.GEOID 64 if (!geoid) return null 65 if (cache[geoid]) return cache[geoid] 66 67 const properties = { 68 ...rawProperties, 69 GEOID: String(geoid), 70 P20: sourceNumber(rawProperties.P20), 71 ALAND20: sourceNumber(rawProperties.ALAND20) 72 } 73 for (const variable of variables) { 74 const key = propertyKey(variable) 75 if (!Object.prototype.hasOwnProperty.call(properties, key)) continue 76 const numeric = Number(properties[key]) 77 if (Number.isFinite(numeric) && numeric < 0) properties[key] = null 78 } 79 const feature = { properties } 80 let value = calculateIndicatorValue(feature, variables, parsed) 81 if (!Number.isFinite(value)) value = null 82 if (config.category_filter_negative && value < 0) return null 83 if (config.category_filter_zero && value === 0) value = null 84 if (config.category_absolute_value && value !== null) value = Math.abs(value) 85 86 let numerator = null 87 let denominator = null
88 if (parsed?.normalize) { 89 const withoutNorm = { ...parsed, normalize: null, rateDivisor: null } 90 numerator = calculateIndicatorValue(feature, variables, withoutNorm) 91 if (!Number.isFinite(numerator)) numerator = null 92 if (config.category_filter_negative && numerator < 0) numerator = null 93 if (config.category_filter_zero && numerator === 0) numerator = null 94 if (config.category_absolute_value && numerator !== null) numerator = Math.abs(numerator) 95 denominator = getAggregationNorm(feature, parsed.normalize) 96 } 97 98 value = rounded(value) 99 numerator = rounded(numerator) 100 denominator = rounded(denominator) 101 const categoryIndex = classifyValue(value, breaks) 102 const adapted = { 103 ...properties, 104 views: { 105 indicator: { 106 value, 107 value_numerator: numerator, 108 value_denominator: denominator, 109 height_value: value, 110 hasUnavailableData: value === null, 111 category_index: categoryIndex, 112 partition_index: 0, 113 color_index: categoryIndex 114 } 115 } 116 } 117 cache[geoid] = adapted 118 return adapted 119 } 120}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.