概述
本教程介绍了如何在 Google 地图上显示来自多个来源的数据。例如,下面的分级统计图使用两个不同的来源突出显示美国各州,并显示特定州的具体数据。
该地图使用 GeoJSON 文件中的数据来显示用于定义美国各州边界的多边形。此外,它还可以在地图上显示各州的对应数据,这些数据来自对 US Census API 的模拟查询。
从控件下拉菜单中选择数据类别即可更新地图上的多边形。您还可以将鼠标悬停在州多边形上,在地图上的数据框控件中查看特定州的具体信息。
以下示例显示了创建此地图所需的完整代码。
TypeScript
const mapStyle: google.maps.MapTypeStyle[] = [ { stylers: [{ visibility: "off" }], }, { featureType: "landscape", elementType: "geometry", stylers: [{ visibility: "on" }, { color: "#fcfcfc" }], }, { featureType: "water", elementType: "geometry", stylers: [{ visibility: "on" }, { color: "#bfd4ff" }], }, ]; let map: google.maps.Map; let censusMin = Number.MAX_VALUE, censusMax = -Number.MAX_VALUE; function initMap(): void { // load the map map = new google.maps.Map(document.getElementById("map") as HTMLElement, { center: { lat: 40, lng: -100 }, zoom: 4, styles: mapStyle, }); // set up the style rules and events for google.maps.Data map.data.setStyle(styleFeature); map.data.addListener("mouseover", mouseInToRegion); map.data.addListener("mouseout", mouseOutOfRegion); // wire up the button const selectBox = document.getElementById( "census-variable" ) as HTMLSelectElement; google.maps.event.addDomListener(selectBox, "change", () => { clearCensusData(); loadCensusData(selectBox.options[selectBox.selectedIndex].value); }); // state polygons only need to be loaded once, do them now loadMapShapes(); } /** Loads the state boundary polygons from a GeoJSON source. */ function loadMapShapes() { // load US state outline polygons from a GeoJson file map.data.loadGeoJson( "https://storage.googleapis.com/mapsdevsite/json/states.js", { idPropertyName: "STATE" } ); // wait for the request to complete by listening for the first feature to be // added google.maps.event.addListenerOnce(map.data, "addfeature", () => { google.maps.event.trigger( document.getElementById("census-variable") as HTMLElement, "change" ); }); } /** * Loads the census data from a simulated API call to the US Census API. * * @param {string} variable */ function loadCensusData(variable: string) { // load the requested variable from the census API (using local copies) const xhr = new XMLHttpRequest(); xhr.open("GET", variable + ".json"); xhr.onload = function () { const censusData = JSON.parse(xhr.responseText) as any; censusData.shift(); // the first row contains column names censusData.forEach((row: string) => { const censusVariable = parseFloat(row[0]); const stateId = row[1]; // keep track of min and max values if (censusVariable < censusMin) { censusMin = censusVariable; } if (censusVariable > censusMax) { censusMax = censusVariable; } const state = map.data.getFeatureById(stateId); // update the existing row with the new data if (state) { state.setProperty("census_variable", censusVariable); } }); // update and display the legend (document.getElementById("census-min") as HTMLElement).textContent = censusMin.toLocaleString(); (document.getElementById("census-max") as HTMLElement).textContent = censusMax.toLocaleString(); }; xhr.send(); } /** Removes census data from each shape on the map and resets the UI. */ function clearCensusData() { censusMin = Number.MAX_VALUE; censusMax = -Number.MAX_VALUE; map.data.forEach((row) => { row.setProperty("census_variable", undefined); }); (document.getElementById("data-box") as HTMLElement).style.display = "none"; (document.getElementById("data-caret") as HTMLElement).style.display = "none"; } /** * Applies a gradient style based on the 'census_variable' column. * This is the callback passed to data.setStyle() and is called for each row in * the data set. Check out the docs for Data.StylingFunction. * * @param {google.maps.Data.Feature} feature */ function styleFeature(feature: google.maps.Data.Feature) { const low = [5, 69, 54]; // color of smallest datum const high = [151, 83, 34]; // color of largest datum let censusVariable = feature.getProperty("census_variable") as number; // delta represents where the value sits between the min and max const delta = (censusVariable - censusMin) / (censusMax - censusMin); const color: number[] = []; for (let i = 0; i < 3; i++) { // calculate an integer color based on the delta color.push((high[i] - low[i]) * delta + low[i]); } // determine whether to show this shape or not let showRow = true; if ( censusVariable == null || isNaN(censusVariable) ) { showRow = false; } let outlineWeight = 0.5, zIndex = 1; if (feature.getProperty("state") === "hover") { outlineWeight = zIndex = 2; } return { strokeWeight: outlineWeight, strokeColor: "#fff", zIndex: zIndex, fillColor: "hsl(" + color[0] + "," + color[1] + "%," + color[2] + "%)", fillOpacity: 0.75, visible: showRow, }; } /** * Responds to the mouse-in event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */ function mouseInToRegion(e: any) { // set the hover state so the setStyle function can change the border e.feature.setProperty("state", "hover"); const percent = ((e.feature.getProperty("census_variable") - censusMin) / (censusMax - censusMin)) * 100; // update the label (document.getElementById("data-label") as HTMLElement).textContent = e.feature.getProperty("NAME"); (document.getElementById("data-value") as HTMLElement).textContent = e.feature .getProperty("census_variable") .toLocaleString(); (document.getElementById("data-box") as HTMLElement).style.display = "block"; (document.getElementById("data-caret") as HTMLElement).style.display = "block"; (document.getElementById("data-caret") as HTMLElement).style.paddingLeft = percent + "%"; } /** * Responds to the mouse-out event on a map shape (state). * */ function mouseOutOfRegion(e: any) { // reset the hover state, returning the border to normal e.feature.setProperty("state", "normal"); } declare global { interface Window { initMap: () => void; } } window.initMap = initMap;
JavaScript
const mapStyle = [ { stylers: [{ visibility: "off" }], }, { featureType: "landscape", elementType: "geometry", stylers: [{ visibility: "on" }, { color: "#fcfcfc" }], }, { featureType: "water", elementType: "geometry", stylers: [{ visibility: "on" }, { color: "#bfd4ff" }], }, ]; let map; let censusMin = Number.MAX_VALUE, censusMax = -Number.MAX_VALUE; function initMap() { // load the map map = new google.maps.Map(document.getElementById("map"), { center: { lat: 40, lng: -100 }, zoom: 4, styles: mapStyle, }); // set up the style rules and events for google.maps.Data map.data.setStyle(styleFeature); map.data.addListener("mouseover", mouseInToRegion); map.data.addListener("mouseout", mouseOutOfRegion); // wire up the button const selectBox = document.getElementById("census-variable"); google.maps.event.addDomListener(selectBox, "change", () => { clearCensusData(); loadCensusData(selectBox.options[selectBox.selectedIndex].value); }); // state polygons only need to be loaded once, do them now loadMapShapes(); } /** Loads the state boundary polygons from a GeoJSON source. */ function loadMapShapes() { // load US state outline polygons from a GeoJson file map.data.loadGeoJson( "https://storage.googleapis.com/mapsdevsite/json/states.js", { idPropertyName: "STATE" }, ); // wait for the request to complete by listening for the first feature to be // added google.maps.event.addListenerOnce(map.data, "addfeature", () => { google.maps.event.trigger( document.getElementById("census-variable"), "change", ); }); } /** * Loads the census data from a simulated API call to the US Census API. * * @param {string} variable */ function loadCensusData(variable) { // load the requested variable from the census API (using local copies) const xhr = new XMLHttpRequest(); xhr.open("GET", variable + ".json"); xhr.onload = function () { const censusData = JSON.parse(xhr.responseText); censusData.shift(); // the first row contains column names censusData.forEach((row) => { const censusVariable = parseFloat(row[0]); const stateId = row[1]; // keep track of min and max values if (censusVariable < censusMin) { censusMin = censusVariable; } if (censusVariable > censusMax) { censusMax = censusVariable; } const state = map.data.getFeatureById(stateId); // update the existing row with the new data if (state) { state.setProperty("census_variable", censusVariable); } }); // update and display the legend document.getElementById("census-min").textContent = censusMin.toLocaleString(); document.getElementById("census-max").textContent = censusMax.toLocaleString(); }; xhr.send(); } /** Removes census data from each shape on the map and resets the UI. */ function clearCensusData() { censusMin = Number.MAX_VALUE; censusMax = -Number.MAX_VALUE; map.data.forEach((row) => { row.setProperty("census_variable", undefined); }); document.getElementById("data-box").style.display = "none"; document.getElementById("data-caret").style.display = "none"; } /** * Applies a gradient style based on the 'census_variable' column. * This is the callback passed to data.setStyle() and is called for each row in * the data set. Check out the docs for Data.StylingFunction. * * @param {google.maps.Data.Feature} feature */ function styleFeature(feature) { const low = [5, 69, 54]; // color of smallest datum const high = [151, 83, 34]; // color of largest datum let censusVariable = feature.getProperty("census_variable"); // delta represents where the value sits between the min and max const delta = (censusVariable - censusMin) / (censusMax - censusMin); const color = []; for (let i = 0; i < 3; i++) { // calculate an integer color based on the delta color.push((high[i] - low[i]) * delta + low[i]); } // determine whether to show this shape or not let showRow = true; if (censusVariable == null || isNaN(censusVariable)) { showRow = false; } let outlineWeight = 0.5, zIndex = 1; if (feature.getProperty("state") === "hover") { outlineWeight = zIndex = 2; } return { strokeWeight: outlineWeight, strokeColor: "#fff", zIndex: zIndex, fillColor: "hsl(" + color[0] + "," + color[1] + "%," + color[2] + "%)", fillOpacity: 0.75, visible: showRow, }; } /** * Responds to the mouse-in event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */ function mouseInToRegion(e) { // set the hover state so the setStyle function can change the border e.feature.setProperty("state", "hover"); const percent = ((e.feature.getProperty("census_variable") - censusMin) / (censusMax - censusMin)) * 100; // update the label document.getElementById("data-label").textContent = e.feature.getProperty("NAME"); document.getElementById("data-value").textContent = e.feature .getProperty("census_variable") .toLocaleString(); document.getElementById("data-box").style.display = "block"; document.getElementById("data-caret").style.display = "block"; document.getElementById("data-caret").style.paddingLeft = percent + "%"; } /** * Responds to the mouse-out event on a map shape (state). * */ function mouseOutOfRegion(e) { // reset the hover state, returning the border to normal e.feature.setProperty("state", "normal"); } window.initMap = initMap;
CSS
html, body, #map { height: 100%; margin: 0; padding: 0; overflow: hidden; } .nicebox { position: absolute; text-align: center; font-family: "Roboto", "Arial", sans-serif; font-size: 13px; z-index: 5; box-shadow: 0 4px 6px -4px #333; padding: 5px 10px; background: rgb(255, 255, 255); background: linear-gradient(to bottom, rgb(255, 255, 255) 0%, rgb(245, 245, 245) 100%); border: rgb(229, 229, 229) 1px solid; } #controls { top: 10px; left: 110px; width: 360px; height: 45px; } #data-box { top: 10px; left: 500px; height: 45px; line-height: 45px; display: none; } #census-variable { width: 360px; height: 20px; } #legend { display: flex; display: -webkit-box; padding-top: 7px; } .color-key { background: linear-gradient(to right, hsl(5, 69%, 54%) 0%, hsl(29, 71%, 51%) 17%, hsl(54, 74%, 47%) 33%, hsl(78, 76%, 44%) 50%, hsl(102, 78%, 41%) 67%, hsl(127, 81%, 37%) 83%, hsl(151, 83%, 34%) 100%); flex: 1; -webkit-box-flex: 1; margin: 0 5px; text-align: left; font-size: 1em; line-height: 1em; } #data-value { font-size: 2em; font-weight: bold; } #data-label { font-size: 2em; font-weight: normal; padding-right: 10px; } #data-label:after { content: ":"; } #data-caret { margin-left: -5px; display: none; font-size: 14px; width: 14px; }
HTML
<html> <head> <title>Mashups with google.maps.Data</title> <link rel="stylesheet" type="text/css" href="./style.css" /> <script type="module" src="./index.js"></script> </head> <body> <div id="controls" class="nicebox"> <div> <select id="census-variable"> <option value="https://storage.googleapis.com/mapsdevsite/json/DP02_0066PE" > Percent of population over 25 that completed high school </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0017E" > Median age </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0001E" > Total population </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP02_0016E" > Average family size </option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP03_0088E" > Per-capita income </option> </select> </div> <div id="legend"> <div id="census-min">min</div> <div class="color-key"><span id="data-caret">◆</span></div> <div id="census-max">max</div> </div> </div> <div id="data-box" class="nicebox"> <label id="data-label" for="data-value"></label> <span id="data-value"></span> </div> <div id="map"></div> <!-- The `defer` attribute causes the script to execute after the full HTML document has been parsed. For non-blocking uses, avoiding race conditions, and consistent behavior across browsers, consider loading using Promises. See https://developers.google.com/maps/documentation/javascript/load-maps-js-api for more information. --> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly" defer ></script> </body> </html>
试用示例
入门指南
您可以使用本教程中的代码开发此分级统计图的自有版本。为此,请首先在文本编辑器中创建一个新文件并将其另存为 index.html
。
请阅读接下来的各个部分,了解可添加到该文件中的代码。
创建基本地图
本部分介绍了用于设置基本地图的代码。这可能与您开始使用 Maps JavaScript API 时创建地图的方式类似。
请将下面的代码复制到 index.html
文件中。这段代码会加载 Maps JavaScript API,还会让地图以全屏方式显示。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <title>Mashups with google.maps.Data</title> <style> #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id="map"></div> <script> function initMap() { // load the map map = new google.maps.Map(document.getElementById('map'), { center: {lat: 40, lng: -100}, zoom: 4, styles: mapStyle }); var mapStyle = [{ 'featureType': 'all', 'elementType': 'all', 'stylers': [{'visibility': 'off'}] }, { 'featureType': 'landscape', 'elementType': 'geometry', 'stylers': [{'visibility': 'on'}, {'color': '#fcfcfc'}] }, { 'featureType': 'water', 'elementType': 'labels', 'stylers': [{'visibility': 'off'}] }, { 'featureType': 'water', 'elementType': 'geometry', 'stylers': [{'visibility': 'on'}, {'hue': '#5f94ff'}, {'lightness': 60}] }]; } </script> <script defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap"> </script> </body> </html>
第一个脚本标记中的代码是程序运行的起点,这段代码创建了一个名为 initMap
的函数来初始化地图对象。
上述代码中的样式器 (stylers) 将地图上所有 featureTypes
(例如道路、地图注点、景观、行政区)及其所有 elementTypes
的可见性都设为关闭。如需查看 featureType
和 elementType
的所有可用值的列表,请参阅 JSON 样式参考。
请点击代码示例中的 YOUR_API_KEY
,或按照相关说明获取 API 密钥。将 YOUR_API_KEY
替换为您应用的 API 密钥。API 完全加载后,以下脚本标记中的回调参数会执行 HTML 文件中的 initMap()
函数。
<script> defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" </script>
创建地图控件并设置其样式
下面的代码会在地图上创建以下控件:
- 一个带有下拉菜单的控件,其中包含 5 个不同的数据选项。
- 一个地图图例。
- 一个用于显示特定州的具体信息的数据框,会在您将鼠标悬停在多边形上时显示。
<div id="controls" class="nicebox"> <div> <select id="census-variable"> <option value="https://storage.googleapis.com/mapsdevsite/json/DP02_0066PE">Percent of population over 25 that completed high school</option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0017E">Median age</option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0001E">Total population</option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP02_0016E">Average family size</option> <option value="https://storage.googleapis.com/mapsdevsite/json/DP03_0088E">Per-capita income</option> </select> </div> <div id="legend"> <div id="census-min">min</div> <div class="color-key"><span id="data-caret">◆</span></div> <div id="census-max">max</div> </div> </div> <div id="data-box" class="nicebox"> <label id="data-label" for="data-value"></label> <span id="data-value"></span> </div>
使用 style
标记内的以下代码设置地图控件的样式。
<style> html, body, #map { height: 100%; margin: 0; padding: 0; overflow: hidden; } .nicebox { position: absolute; text-align: center; font-family: "Roboto", "Arial", sans-serif; font-size: 13px; z-index: 5; box-shadow: 0 4px 6px -4px #333; padding: 5px 10px; background: rgb(255,255,255); background: linear-gradient(to bottom,rgba(255,255,255,1) 0%,rgba(245,245,245,1) 100%); border: rgb(229, 229, 229) 1px solid; } #controls { top: 10px; left: 110px; width: 360px; height: 45px; } #data-box { top: 10px; left: 500px; height: 45px; line-height: 45px; display: none; } #census-variable { width: 360px; height: 20px; } #legend { display: flex; display: -webkit-box; padding-top: 7px } .color-key { background: linear-gradient(to right, hsl(5, 69%, 54%) 0%, hsl(29, 71%, 51%) 17%, hsl(54, 74%, 47%) 33%, hsl(78, 76%, 44%) 50%, hsl(102, 78%, 41%) 67%, hsl(127, 81%, 37%) 83%, hsl(151, 83%, 34%) 100%); flex: 1; -webkit-box-flex: 1; margin: 0 5px; text-align: left; font-size: 1.0em; line-height: 1.0em; } #data-value { font-size: 2.0em; font-weight: bold } #data-label { font-size: 2.0em; font-weight: normal; padding-right: 10px; } #data-label:after { content: ':' } #data-caret { margin-left: -5px; display: none; font-size: 14px; width: 14px} </style>
从 US Census API 导入数据
以下代码会向美国普查局查询美国各州的最新人口普查数据,并以 JSON 格式接收这些数据。
function loadCensusData(variable) { // load the requested variable from the census API var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.census.gov/data/2012/acs5/profile?get=' + variable + '&for=state:*&key=YOUR_API_KEY'); xhr.onload = function() { var censusData = JSON.parse(xhr.responseText); censusData.shift(); // the first row contains column names censusData.forEach(function(row) { var censusVariable = parseFloat(row[0]); var stateId = row[1]; // keep track of min and max values if (censusVariable < censusMin) { censusMin = censusVariable; } if (censusVariable > censusMax) { censusMax = censusVariable; } // update the existing row with the new data map.data .getFeatureById(stateId) .setProperty('census_variable', censusVariable); }); // update and display the legend document.getElementById('census-min').textContent = censusMin.toLocaleString(); document.getElementById('census-max').textContent = censusMax.toLocaleString(); }; xhr.send(); }
设置数据样式
以下代码根据人口普查数据值,通过向数据集内的各个多边形应用渐变来创建分级统计图。您可以使用 Data.StyleOptions
对象或返回 Data.StyleOptions
对象的函数设置数据样式。
// set up the style rules and events for google.maps.Data map.data.setStyle(styleFeature); function styleFeature(feature) { var low = [5, 69, 54]; // color of smallest datum var high = [151, 83, 34]; // color of largest datum // delta represents where the value sits between the min and max var delta = (feature.getProperty('census_variable') - censusMin) / (censusMax - censusMin); var color = []; for (var i = 0; i < 3; i++) { // calculate an integer color based on the delta color[i] = (high[i] - low[i]) * delta + low[i]; } // determine whether to show this shape or not var showRow = true; if (feature.getProperty('census_variable') == null || isNaN(feature.getProperty('census_variable'))) { showRow = false; } var outlineWeight = 0.5, zIndex = 1; if (feature.getProperty('state') === 'hover') { outlineWeight = zIndex = 2; } return { strokeWeight: outlineWeight, strokeColor: '#fff', zIndex: zIndex, fillColor: 'hsl(' + color[0] + ',' + color[1] + '%,' + color[2] + '%)', fillOpacity: 0.75, visible: showRow }; }
除了为多边形着色之外,以下代码还会通过添加用于响应鼠标活动的事件来创建互动元素。将鼠标悬停在多边形上可突出显示州边界,同时还可更新地图上的数据框控件。
// set up the style rules and events for google.maps.Data map.data.addListener('mouseover', mouseInToRegion); map.data.addListener('mouseout', mouseOutOfRegion); /** * Responds to the mouse-in event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */ function mouseInToRegion(e) { // set the hover state so the setStyle function can change the border e.feature.setProperty('state', 'hover'); var percent = (e.feature.getProperty('census_variable') - censusMin) / (censusMax - censusMin) * 100; // update the label document.getElementById('data-label').textContent = e.feature.getProperty('NAME'); document.getElementById('data-value').textContent = e.feature.getProperty('census_variable').toLocaleString(); document.getElementById('data-box').style.display = 'block'; document.getElementById('data-caret').style.display = 'block'; document.getElementById('data-caret').style.paddingLeft = percent + '%'; } /** * Responds to the mouse-out event on a map shape (state). * * @param {?google.maps.MapMouseEvent} e */ function mouseOutOfRegion(e) { // reset the hover state, returning the border to normal e.feature.setProperty('state', 'normal'); }
加载州边界多边形
请在整个 initMap
函数后面添加以下代码。loadMapShapes
函数使用 loadGeoJson
方法从 GeoJSON 文件加载美国各州边界的多边形。
/** Loads the state boundary polygons from a GeoJSON source. */ function loadMapShapes() { // load US state outline polygons from a GeoJSON file map.data.loadGeoJson('https://storage.googleapis.com/mapsdevsite/json/states.js', { idPropertyName: 'STATE' });
将下面这行代码添加到 initMap
函数的末尾。
// state polygons only need to be loaded once, do them now loadMapShapes();
从地图控件下拉菜单中选择数据源选项时,地图会向 US Census Data API 查询指定变量。为了将人口普查数据与形状数据关联起来,上述代码会将 idPropertyName
设置为“STATE”,这是人口普查数据与 GeoJson 属性所共有的键。
更多信息
此演示使用的是 Census Bureau Data API,但未获得美国普查局的背书或认证。