var mapCont = document.getElementById('map'); var margin = mapCont.offsetTop * 2; mapCont.style.height = 'Calc(100vh - ' + margin + 'px)'; var abuseData = { geoJsons: { county: { layer: null, data: null, ready: false, url: "./geojson/cb_2022_us_county_500k.geojson" }, state: { layer: null, data: null, ready: false, url: "./geojson/cb_2022_us_state_500k.geojson" } } }; var clusteredMarkers; var fnQueue = []; var abuseDetailPanel; var map = L.map('map', { renderer: L.canvas() }).setView([37.8, -96], 4); map.createPane('labels'); map.getPane('labels').style.zIndex = 450; map.getPane('labels').style.pointerEvents = 'none'; map.getPane('labels').style.opacity = .5; map.createPane('terrainlines'); map.getPane('terrainlines').style.zIndex = 425; map.getPane('terrainlines').style.pointerEvents = 'none'; map.getPane('terrainlines').style.opacity = .25; var CartoDB_PositronNoLabels = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors © CARTO ' + 'Stadia Maps Stamen Design OpenMapTiles', subdomains: 'abcd', maxZoom: 20 }).addTo(map); var Stadia_StamenTerrainLines = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_terrain_lines/{z}/{x}/{y}{r}.{ext}', { minZoom: 0, maxZoom: 18, ext: 'png', pane: 'terrainlines' }).addTo(map); var CartoDB_VoyagerOnlyLabels = L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}{r}.png', { subdomains: 'abcd', maxZoom: 20, pane: 'labels' }).addTo(map); function geoJsonsReady() { var allReady = true; Object.values(abuseData.geoJsons).forEach((g) => { if (!g.ready) allReady = false; }); return allReady; } function loadGeoJsons() { var checkComplete = function (node) { node.ready = true; if (geoJsonsReady()) { while (fnQueue.length > 0) { let work = fnQueue.shift(); work.fnc.apply(null, work.arg); } } }; Object.values(abuseData.geoJsons).forEach((gj) => { fetch(gj.url).then(r => r.json()).then((d) => { gj.data = d; checkComplete(gj); }); }); } function zoomToFeature(e) { map.fitBounds(e.target.getBounds()); } function onEachFeature(feature, layer) { if (feature.properties.incidents == 0) return; var html; if (feature.properties.NAMELSAD && feature.properties.incidents == 1) { html = `${feature.properties.NAMELSAD}
1 incident`; } else if (feature.properties.NAMELSAD) { html = `${feature.properties.NAMELSAD}
${feature.properties.incidents} incidents`; } else if (feature.properties.NAME && feature.properties.incidents == 1) { html = `${feature.properties.NAME}
1 incident`; } else { html = `${feature.properties.NAME}
${feature.properties.incidents} incidents`; } layer.bindTooltip(html, { interactive: false, sticky: true }); layer.on('click', zoomToFeature); } function showBoundaryLayer() { if (!geoJsonsReady()) return; var z = map.getZoom(); if (z >= 8.5) { if (map.hasLayer(abuseData.geoJsons.state.layer)) abuseData.geoJsons.state.layer.remove(); if (map.hasLayer(abuseData.geoJsons.county.layer)) abuseData.geoJsons.county.layer.remove(); if (clusteredMarkers && !map.hasLayer(clusteredMarkers)) map.addLayer(clusteredMarkers); if (abuseDetailPanel && !map.hasLayer(abuseDetailPanel)) abuseDetailPanel.addTo(map); } else if (z > 4) { if (map.hasLayer(abuseData.geoJsons.state.layer)) abuseData.geoJsons.state.layer.remove(); if (!map.hasLayer(abuseData.geoJsons.county.layer)) abuseData.geoJsons.county.layer.addTo(map); if (clusteredMarkers && !map.hasLayer(clusteredMarkers)) map.addLayer(clusteredMarkers); if (abuseDetailPanel && !map.hasLayer(abuseDetailPanel)) abuseDetailPanel.addTo(map); } else { if (map.hasLayer(abuseData.geoJsons.county.layer)) abuseData.geoJsons.county.layer.remove(); if (!map.hasLayer(abuseData.geoJsons.state.layer)) abuseData.geoJsons.state.layer.addTo(map); if (clusteredMarkers && !map.hasLayer(clusteredMarkers)) map.addLayer(clusteredMarkers); if (abuseDetailPanel && !map.hasLayer(abuseDetailPanel)) abuseDetailPanel.addTo(map); } } function receiveDataLayers(layers, catDetails) { if (!geoJsonsReady()) { fnQueue.push({ fnc: receiveDataLayers, arg: [layers, catDetails] }); return; } clearDataLayers(); var catValueColors = []; var legendItems = { title: '', catValues: {} }; if (catDetails) { catValueColors = (chroma.scale('Paired').colors(catDetails.length)).reverse(); catValueColors.forEach((c, i) => { if (c == '#ffffd9') catValueColors[i] = '#858585'; }); legendItems.title = `

${Object.keys(catDetails[0])[0]} Distribution

`; catDetails.forEach((c, idx) => { var items = Object.values(c); legendItems.catValues[items[0]] = catValueColors[idx]; }); } else { catValueColors = (chroma.scale('Paired').colors(1)).reverse(); catValueColors.forEach((c, i) => { if (c == '#ffffd9') catValueColors[i] = '#858585'; }); legendItems = { title: `

All Incidents

`, catValues: { 'All Locations': catValueColors[0] } }; } abuseDetailPanel.onAdd = function (map) { var div = L.DomUtil.create('div', 'info detailPanel'); /*var labelTopHTML = ''; var labelTopSpacerHTML = ''; var labelColorHTML = ''; var labelBottomSpacerHTML = ''; var labelBottomHTML = ''; var sectionLength = (100 / catValueColors.length) - .1; Object.entries(legendItems.catValues).forEach((e, i) => { if (i % 2 == 0) { labelTopHTML += `
${e[0]}
`; labelTopSpacerHTML += `
 
 
`; labelBottomHTML += `
 
`; labelBottomSpacerHTML += `
 
`; } else { labelBottomHTML += `
${e[0]}
`; labelBottomSpacerHTML += `
 
 
`; labelTopHTML += `
 
`; labelTopSpacerHTML += `
 
`; } labelColorHTML += `
 
`; }); var html = `
${legendItems.title}
${labelTopHTML + "
" + labelTopSpacerHTML + "
" + labelColorHTML + "
" + labelBottomSpacerHTML + "
" + labelBottomHTML}
`;*/ var legendKey = ""; Object.entries(legendItems.catValues).forEach((e, i) => { legendKey += '
' + '' + '
' + e[0] + '
' + '
'; }); var wedge = 360 / catValueColors.length; var pieCSS = []; var startDeg = 0; for (var i = 0; i < catValueColors.length; i++) { pieCSS.push(`${catValueColors[i]} ${(wedge * i)}deg, ${catValueColors[i]} ${((i + 1) * wedge)}deg`); } var html = `
${legendItems.title}
 
${legendKey}
`; //var html = `
 
`; div.innerHTML = html; return div; }; var markers = []; for (var i = 0; i < layers.length; i++) { var lat = layers[i].Latitude; var lng = layers[i].Longitude; var incident = layers[i]; var o; if (catDetails) { o = {}; var cat = Object.keys(catDetails[0])[0]; var catValue = incident[cat + ' Value']; var oo; if (!catValue || catValue == '') continue; if (catValue.indexOf('|') > -1) { catValue = catValue.split('|'); oo = { 'category': cat, 'categoryValue': catValue, 'childNumber': incident['Child Number'] }; catValue.forEach((v) => { oo['catValueColor'] = oo['catValueColor'] ? oo['catValueColor'].concat(legendItems.catValues[v]) : new Array(legendItems.catValues[v]); }); o = oo; } else { oo = { 'category': cat, 'categoryValue': catValue, 'catValueColor': legendItems.catValues[catValue] == '#ffffd9' ? '#858585' : legendItems.catValues[catValue], 'childNumber': incident['Child Number'] }; o = oo; } } else { o = {}; var oo = { 'category': 'All Incidents', 'categoryValue': 'All Locations', 'catValueColor': catValueColors[0] == '#ffffd9' ? '#858585' : catValueColors[0], 'childNumber': incident['Child Number'] }; o = oo; } //temporary fix to pick one color for multi answer incidents. if (Array.isArray(o['catValueColor'])) o['catValueColor'] = o['catValueColor'][0]; var title = 'Click for details'; var html = `

location: ${(layers[i]['Incident City'] ? layers[i]['Incident City'] + ', ' : '') + (layers[i]['Incident County'] ? layers[i]['Incident County'] + ', ' : '') + (layers[i]['Incident State'] ? layers[i]['Incident State'] : '')}

`; html += `

# of children affected: ${layers[i]['Child Number']}

`; html += `

year of incident: ${!layers[i]['Incident Year'] || layers[i]['Incident Year'] == 0 ? 'not available' : layers[i]['Incident Year']}

`; html += `

distribution: ${o['categoryValue']}

`; var marker = L.circleMarker(new L.LatLng(lat, lng), { title: title, pane: 'markerPane', color: '#000000', weight: 1, opacity: .3, fill: true, fillOpacity: 1, fillColor: o['catValueColor'] }); marker['incidentGroup'] = o; marker.bindPopup(html); markers.push(marker); if (layers[i]['Incident State GEO_ID']) { var ft = abuseData.geoJsons.state.data.features.find((f) => f.properties.GEOID == layers[i]['Incident State GEO_ID']); if (ft) { ft.properties.incidents += 1; } } if (layers[i]['Incident County GEO_ID']) { var ft = abuseData.geoJsons.county.data.features.find((f) => f.properties.GEOID == layers[i]['Incident County GEO_ID']); if (ft) { ft.properties.incidents += 1; } } } clusteredMarkers.addLayers(markers, { pane: 'terrainlinesPane' }).addTo(map); abuseDetailPanel.addTo(map); var bins = abuseData.geoJsons.county.data.features.filter((f) => f.properties.incidents > 0); abuseData.geoJsons.county.layer = L.dataClassification(abuseData.geoJsons.county.data, { style: { color: "rgba(233, 234, 235, .5)" }, mode: 'jenks', classes: bins.length >= 4 ? 5 : 3, field: 'incidents', pointMode: 'color', colorRamp: 'YlOrRd', classRounding: 0, legendTitle: 'Incidents by County', legendPosition: 'bottomright', onEachFeature: onEachFeature }); bins = abuseData.geoJsons.state.data.features.filter((f) => f.properties.incidents > 0); abuseData.geoJsons.state.layer = L.dataClassification(abuseData.geoJsons.state.data, { style: { color: "rgba(233, 234, 235, .5)" }, mode: 'jenks', classes: bins.length >= 4 ? 5 : 3, field: 'incidents', pointMode: 'color', colorRamp: 'YlOrRd', classRounding: 0, legendTitle: 'Incidents by State', legendPosition: 'bottomright', onEachFeature: onEachFeature }); map.on('zoomend', showBoundaryLayer); if (!catDetails) { if (map.getZoom() == 3) showBoundaryLayer(); map.setView([37.8, -96], 3); return; } showBoundaryLayer(); } function createClusterIcon(cluster) { var markers = cluster.getAllChildMarkers(); var html = ''; var n = 0; var objColors = [] for (var i = 0; i < markers.length; i++) { //keep it to just the one selected category for now var o = markers[i]['incidentGroup']; var catValue = o['categoryValue']; n += Array.isArray(catValue) ? catValue.length : 1; if (objColors.indexOf(o['catValueColor']) < 0) objColors.push(o['catValueColor']); } var wedge = 360 / objColors.length; var pieCSS = []; var startDeg = 0; for (var i = 0; i < objColors.length; i++) { pieCSS.push(`${objColors[i]} ${(wedge * i)}deg, ${objColors[i]} ${((i + 1) * wedge)}deg`); } var html = `
${n}
`; return L.divIcon({ html: html, className: 'pieChart', iconSize: L.point(40, 40) }); } function clearDataLayers() { map.off('zoomend', showBoundaryLayer); if (clusteredMarkers && clusteredMarkers.getLayers().length > 0) { clusteredMarkers.clearLayers(); } if (clusteredMarkers && map.hasLayer(clusteredMarkers)) map.removeLayer(clusteredMarkers); clusteredMarkers = L.markerClusterGroup({ showCoverageOnHover: false, iconCreateFunction: createClusterIcon }); Object.values(abuseData.geoJsons).forEach((gj) => { if (gj.layer && map.hasLayer(gj.layer)) gj.layer.remove(); if (gj.data && gj.ready && gj.data.features) gj.data.features.forEach(f => { f.properties['incidents'] = 0; }); }); if (abuseDetailPanel) abuseDetailPanel.remove(); abuseDetailPanel = L.control({ position: 'bottomleft' }); } loadGeoJsons();