重叠多边形引发填充颜色透明度不一致的技术问询
解决多边形重叠时透明度叠加导致颜色不均的问题
问题描述
我们通过生成含360个顶点的多边形模拟红色、10%透明度的圆形,但多个此类多边形重叠时,重叠区域因颜色叠加效应,透明度高于预期的10%,出现部分区域颜色过深、透明度不一致的情况。需要实现无论多边形是否重叠,所有圆形填充色的透明度始终稳定在10%。
当前效果

期望效果

核心原因
原实现中每个多边形单独设置了带10%透明度的填充色,绘制时重叠区域会多次叠加透明度(例如两个重叠区域的透明度为 1 - (1-0.1)*(1-0.1) = 0.19),导致颜色变深。
解决方案
提供两种可行方案,根据业务场景选择:
方案1:合并重叠多边形(推荐)
将所有需要绘制的重叠多边形合并为一个单一多边形(计算几何并集),这样整个区域仅绘制一次,自然不会出现透明度叠加问题。可以使用turf库的多边形合并功能:
- 引入
turf依赖(在pubspec.yaml中添加):
dependencies: turf: ^0.1.0
- 修改代码合并多边形:
import 'package:turf/turf.dart'; // 收集所有需要绘制的多边形坐标列表 List<List<Position>> allPolygonCoordinates = []; for (var resultItem in result) { List<Position> points = listgeneratePolygonPoints( resultItem.lat, resultItem.lng, resultItem.radius, ).map((p) => Position(p.lng, p.lat)).toList(); allPolygonCoordinates.add(points); } // 合并所有多边形为一个 Feature<Polygon>? mergedPolygon; for (var coords in allPolygonCoordinates) { var polygon = Polygon(coordinates: [coords]); if (mergedPolygon == null) { mergedPolygon = Feature(geometry: polygon); } else { mergedPolygon = union(mergedPolygon, Feature(geometry: polygon)); } } // 创建单个合并后的多边形注解 if (mergedPolygon != null) { nonSmokingAreas["merged"] = await nonSmokingAreaManager?.create( PolygonAnnotationOptions( fillColor: Colors.red.withOpacity(0.1).value, fillSortKey: 1, geometry: mergedPolygon.geometry!.toJson(), ), ); }
方案2:使用图层级透明度与混合模式
不通过单个注解设置带透明度的颜色,而是将所有多边形放在同一个图层中,设置图层级的透明度,并使用src混合模式避免叠加:
// 创建GeoJSON数据源 await mapboxMap.style.addSource( GeoJsonSource( id: "non-smoking-source", data: { "type": "FeatureCollection", "features": result.map((item) { List<Position> points = listgeneratePolygonPoints( item.lat, item.lng, item.radius, ).map((p) => Position(p.lng, p.lat)).toList(); return { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [points.map((p) => [p.lng, p.lat]).toList()] } }; }).toList(), }, ), ); // 创建填充图层,设置全局透明度与混合模式 await mapboxMap.style.addLayer( FillLayer( id: "non-smoking-layer", source: "non-smoking-source", fillColor: Colors.red.value, // 不设置颜色内的透明度 fillOpacity: 0.1, // 图层级别的统一透明度 fillBlendMode: BlendMode.src, // 确保重叠区域不叠加 ), );
方案3:调整单个注解的透明度设置
如果必须保留单个注解,可将颜色透明度与fillOpacity分离,避免叠加:
nonSmokingAreas[result[i].id] = (await nonSmokingAreaManager?.create( PolygonAnnotationOptions( fillColor: Colors.red.value, // 纯红色,不包含透明度 fillOpacity: 0.1, // 单独设置注解的透明度 fillSortKey: 1, fillBlendMode: BlendMode.src, // 若API支持则添加 geometry: Polygon( coordinates: [ List.generate( points.length, (index) => Position( points[index].lng, points[index].lat, ), ), ], ).toJson()), ))!;
原生成多边形代码(无需修改)
import 'dart:math'; /// 生成围绕指定地理坐标、指定半径的正多边形顶点列表 List<({double lat, double lng})> listgeneratePolygonPoints( double lat, double lng, double radius) { List<({double lat, double lng})> points = []; for (int i = 0; i < 360; i++) { double angle = (2 * pi * i) / 360; double dx = radius * cos(angle); double dy = radius * sin(angle); final point = _translateCoordinate((lat: lat, lng: lng), dx, dy); points.add(point); } return points; } /// 根据原点位置、经纬位移量转换地理坐标 ({double lat, double lng}) _translateCoordinate( ({double lat, double lng}) coordinate, double dx, double dy) { const double earthRadius = 6378137; // 地球半径(米) double lat = coordinate.lat + (dy / earthRadius) * (180 / pi); double lon = coordinate.lng + (dx / earthRadius) * (180 / pi) / cos(coordinate.lat * pi / 180); return (lat: lat, lng: lon); }
内容的提问来源于stack exchange,提问作者Kyrill
相关产品推荐
相关产品推荐

