如何解决amCharts 5地图投影中Rhumb Line的环绕问题?
解决amCharts 5恒向线跨地图边缘环绕问题
核心问题
当使用panX: "rotateX"的矩形投影(如墨卡托)时,amCharts默认会自动将跨180°经线的线段环绕绘制,导致恒向线错误地绕世界一圈。需要禁用自动环绕,并确保跨边缘的线段被拆分为独立的不连续段。
修改方案
- 禁用线的自动环绕:给
MapLineSeries设置noWrap: true,阻止amCharts自动连接跨边缘的点。 - 优化恒向线拆分逻辑:将穿过180°经线的线段拆分为多个独立的
LineString,而不是在同一条线中插入-180/180的点,确保每个线段都在可视范围内。
修改后的完整代码
JavaScript
am5.ready(function() { // Create root and chart var root = am5.Root.new("chartdiv"); var chart = root.container.children.push( am5map.MapChart.new(root, { panX: "rotateX", panY: "none", projection: am5map.geoNaturalEarth1() }) ); // Set themes root.setThemes([ am5themes_Animated.new(root) ]); // Create polygon series var polygonSeries = chart.series.push( am5map.MapPolygonSeries.new(root, { geoJSON: am5geodata_worldLow }) ); var graticuleSeries = chart.series.insertIndex( 0, am5map.GraticuleSeries.new(root, {}) ); graticuleSeries.mapLines.template.setAll({ stroke: am5.color(0x000000), strokeOpacity: 0.1 }); var backgroundSeries = chart.series.unshift( am5map.MapPolygonSeries.new(root, {}) ); backgroundSeries.mapPolygons.template.setAll({ fill: am5.color(0xedf7fa), stroke: am5.color(0xedf7fa), }); backgroundSeries.data.push({ geometry: am5map.getGeoRectangle(90, 180, -90, -180) }); setProjection("geoAiry"); function setProjection(name) { chart.set("projection", d3[name].call(this)); setButtonState(); // 重新绘制恒向线 redrawRhumbLine(); } function setIndex(offset) { var selector = document.getElementById("selector"); var index = selector.selectedIndex + offset; if (index < 0) { index = 0; } if (index > (selector.options.length - 1)) { index = selector.options.length - 1; } selector.selectedIndex = index; setProjection(selector.options[index].value); setButtonState(); } function setButtonState() { var selector = document.getElementById("selector"); var index = selector.selectedIndex; if (index == 0) { document.getElementById("selector-prev").disabled = "disabled"; document.getElementById("selector-next").disabled = ""; } else if (index >= (selector.options.length - 1)) { document.getElementById("selector-prev").disabled = ""; document.getElementById("selector-next").disabled = "disabled"; } else { document.getElementById("selector-prev").disabled = ""; document.getElementById("selector-next").disabled = ""; } } // Create a line series with noWrap enabled var lineSeries = chart.series.push(am5map.MapLineSeries.new(root, { lineType: "straight", noWrap: true // 禁用自动环绕 })); lineSeries.stroke = am5.color("#FF0000"); // Functions to calculate Rhumb Line segments function toRad(x) { return x * Math.PI / 180; } function toDeg(x) { return x * 180 / Math.PI; } function calculateRhumbLineSegments(start, end, numPoints = 100) { let segments = []; let currentSegment = []; let lat1 = toRad(start.latitude); let lon1 = toRad(start.longitude); let lat2 = toRad(end.latitude); let lon2 = toRad(end.longitude); let dLon = lon2 - lon1; let dPhi = Math.log(Math.tan(lat2 / 2 + Math.PI / 4) / Math.tan(lat1 / 2 + Math.PI / 4)); if (Math.abs(lon2 - lon1) > Math.PI) { if (lon2 <= lon1) { dLon = ((lon2 + 2 * Math.PI) - lon1); } else { dLon = ((lon2 - 2 * Math.PI) - lon1); } } // Normalize to -180..+180 lon1 = (lon1 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; lon2 = (lon2 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; let lastLon = lon1; // 添加起点到当前线段 currentSegment.push({ latitude: toDeg(lat1), longitude: toDeg(lon1) }); for (let i = 1; i <= numPoints; i++) { let f = i / numPoints; let lat = lat1 + (lat2 - lat1) * f; let lon = lon1 + dLon * f; let degLon = toDeg(lon); let degLat = toDeg(lat); // 检测是否跨180度经线 if (Math.abs(lon - lastLon) > Math.PI) { // 计算跨边缘的中间点(分别在-180和180处) let midLat = toDeg((lat + toRad(currentSegment[currentSegment.length - 1].latitude)) / 2); // 结束当前线段在边缘处 if (lon > lastLon) { currentSegment.push({ latitude: midLat, longitude: -180 }); } else { currentSegment.push({ latitude: midLat, longitude: 180 }); } segments.push(currentSegment); // 开始新的线段在对面边缘 currentSegment = []; if (lon > lastLon) { currentSegment.push({ latitude: midLat, longitude: 180 }); } else { currentSegment.push({ latitude: midLat, longitude: -180 }); } } currentSegment.push({ latitude: degLat, longitude: degLon }); lastLon = lon; } // 添加最后一个线段 segments.push(currentSegment); return segments; } function redrawRhumbLine() { // 清除之前的线 lineSeries.data.clear(); // Calculate Rhumb Line segments from New York to London var newYork = { latitude: 40.7128, longitude: -74.0060 }; var london = { latitude: 51.5074, longitude: -0.1278 }; var rhumbLineSegments = calculateRhumbLineSegments(newYork, london); // 为每个线段创建独立的线 rhumbLineSegments.forEach(function(segment) { if (segment.length >= 2) { var coordinates = segment.map(point => [point.longitude, point.latitude]); lineSeries.pushDataItem({ geometry: { type: "LineString", coordinates: coordinates } }); } }); } // 初始化绘制 redrawRhumbLine(); // Expose the functions to the global scope window.setProjection = setProjection; window.setIndex = setIndex; }); // end am5.ready()
CSS
.tools { text-align: center; } .tools select, .tools input { font-size: 1.2em; padding: 0.2em 0.4em; } #chartdiv { width: 100%; height: 500px; }
HTML
<div class="tools"> <input id="selector-prev" type="button" value="<" onclick="setIndex(-1);" disabled="disabled" /> <select id="selector" onchange="setProjection(this.options[this.selectedIndex].value);"> <option value="geoAiry">d3.geoAiry()</option> <option value="geoMercator">d3.geoMercator()</option> </select> <input id="selector-next" type="button" value=">" onclick="setIndex(1);" /> </div> <div id="chartdiv"></div> <script src="https://cdn.amcharts.com/lib/5/index.js"></script> <script src="https://cdn.amcharts.com/lib/5/map.js"></script> <script src="https://cdn.amcharts.com/lib/5/geodata/worldLow.js"></script> <script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script> <script src="https://d3js.org/d3-array.v1.min.js"></script> <script src="https://d3js.org/d3-geo.v1.min.js"></script> <script src="https://d3js.org/d3-geo-projection.v2.min.js"></script>
关键改动说明
noWrap: true:添加到MapLineSeries配置中,告诉amCharts不要自动将跨180°经线的线段连接成环绕世界的线。- 拆分线段为独立数组:
calculateRhumbLineSegments函数不再返回单个点数组,而是返回多个线段数组,每个线段都是不跨180°的连续点集,确保每个线段在地图上独立显示,不会被错误连接。 - 重新绘制逻辑:添加
redrawRhumbLine函数,切换投影时重新绘制恒向线,保证投影变化后线段正确显示。
内容的提问来源于stack exchange,提问作者Zarty
相关产品推荐
相关产品推荐

