Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: overlapping Polygon cutting & color/translucency mixing #1901

Merged
merged 3 commits into from
Jun 8, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions example/lib/pages/polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,71 @@ class _PolygonPageState extends State<PolygonPage> {
subtitle: 'This one still works with performant rendering',
),
),
Polygon(
points: const [
LatLng(61.861042, 0.946502),
LatLng(61.861458, 0.949468),
LatLng(61.861427, 0.949626),
LatLng(61.859015, 0.951513),
LatLng(61.858129, 0.952652)
],
holePointsList: [],
color: Colors.lightGreen.withOpacity(0.5),
borderColor: Colors.lightGreen.withOpacity(0.5),
borderStrokeWidth: 10,
hitValue: (
title: 'Testing opacity treatment (small)',
subtitle:
"Holes shouldn't be cut, and colors should be mixed correctly",
),
),
Polygon(
points: const [
LatLng(61.861042, 0.946502),
LatLng(61.861458, 0.949468),
LatLng(61.861427, 0.949626),
LatLng(61.859015, 0.951513),
LatLng(61.858129, 0.952652),
LatLng(61.857633, 0.953214),
LatLng(61.855842, 0.954683),
LatLng(61.855769, 0.954692),
LatLng(61.855679, 0.954565),
LatLng(61.855417, 0.953926),
LatLng(61.855268, 0.953431),
LatLng(61.855173, 0.952443),
LatLng(61.855161, 0.951147),
LatLng(61.855222, 0.950822),
LatLng(61.855928, 0.948422),
LatLng(61.856365, 0.946638),
LatLng(61.856456, 0.946586),
LatLng(61.856787, 0.946656),
LatLng(61.857578, 0.946675),
LatLng(61.859338, 0.946453),
LatLng(61.861042, 0.946502)
],
holePointsList: const [
[
LatLng(61.858881, 0.947234),
LatLng(61.858728, 0.947126),
LatLng(61.858562, 0.947132),
LatLng(61.858458, 0.947192),
LatLng(61.85844, 0.947716),
LatLng(61.858488, 0.947819),
LatLng(61.858766, 0.947818),
LatLng(61.858893, 0.947779),
LatLng(61.858975, 0.947542),
LatLng(61.858881, 0.947234)
]
],
color: Colors.lightGreen.withOpacity(0.5),
borderColor: Colors.lightGreen.withOpacity(0.5),
borderStrokeWidth: 10,
hitValue: (
title: 'Testing opacity treatment (large)',
subtitle:
"Holes shouldn't be cut, and colors should be mixed correctly",
),
),
];
late final _polygons =
Map.fromEntries(_polygonsRaw.map((e) => MapEntry(e.hitValue, e)));
Expand Down
46 changes: 37 additions & 9 deletions lib/src/layer/polygon_layer/painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@ base class _PolygonPainter<R extends Object>
/// Reference to the bounding box of the [Polygon].
final LatLngBounds bounds;

/// Whether to draw per-polygon labels
/// Whether to draw per-polygon labels ([Polygon.label])
///
/// Note that drawing labels will reduce performance, as the internal
/// canvas must be drawn to and 'saved' more frequently to ensure the proper
/// stacking order is maintained. This can be avoided, potentially at the
/// expense of appearance, by setting [PolygonLayer.drawLabelsLast].
///
/// It is safe to ignore this property, and the performance pitfalls described
/// above, if no [Polygon]s have labels specified.
final bool polygonLabels;

/// Whether to draw labels last and thus over all the polygons
///
/// This may improve performance: see [polygonLabels] for more information.
final bool drawLabelsLast;

/// Create a new [_PolygonPainter] instance.
Expand Down Expand Up @@ -85,6 +95,8 @@ base class _PolygonPainter<R extends Object>

@override
void paint(Canvas canvas, Size size) {
const checkOpacity = true; // for debugging purposes only, should be true

final trianglePoints = <Offset>[];

final filledPath = Path();
Expand Down Expand Up @@ -154,8 +166,12 @@ base class _PolygonPainter<R extends Object>
// The hash is based on the polygons visual properties. If the hash from
// the current and the previous polygon no longer match, we need to flush
// the batch previous polygons.
// We also need to flush if the opacity is not 1 or 0, so that they get
// mixed properly. Otherwise, holes get cut, or colors aren't mixed,
// depending on the holes handler.
final hash = polygon.renderHashCode;
if (lastHash != hash) {
final opacity = polygon.color?.opacity ?? 0;
if (lastHash != hash || (checkOpacity && opacity > 0 && opacity < 1)) {
drawPaths();
}
lastPolygon = polygon;
Expand Down Expand Up @@ -193,13 +209,11 @@ base class _PolygonPainter<R extends Object>
}

// Afterwards deal with more complicated holes.
// Improper handling of opacity and fill methods may result in normal
// polygons cutting holes into other polygons, when they should be mixing:
// https://github.com/fleaflet/flutter_map/issues/1898.
final holePointsList = polygon.holePointsList;
if (holePointsList != null && holePointsList.isNotEmpty) {
// Ideally we'd use `Path.combine(PathOperation.difference, ...)`
// instead of evenOdd fill-type, however it creates visual artifacts
// using the web renderer.
filledPath.fillType = PathFillType.evenOdd;

final holeOffsetsList = List<List<Offset>>.generate(
holePointsList.length,
(i) => getOffsets(camera, origin, holePointsList[i]),
Expand All @@ -208,11 +222,25 @@ base class _PolygonPainter<R extends Object>

for (final holeOffsets in holeOffsetsList) {
filledPath.addPolygon(holeOffsets, true);

/* https://github.com/flutter/flutter/issues/44572
filledPath = Path.combine(
PathOperation.difference,
filledPath,
Path()..addPolygon(holeOffsets, true),
);*/
JaffaKetchup marked this conversation as resolved.
Show resolved Hide resolved
}

if (!polygon.disableHolesBorder && polygon.borderStrokeWidth > 0.0) {
_addHoleBordersToPath(borderPath, polygon, holeOffsetsList, size,
canvas, _getBorderPaint(polygon), polygon.borderStrokeWidth);
_addHoleBordersToPath(
borderPath,
polygon,
holeOffsetsList,
size,
canvas,
_getBorderPaint(polygon),
polygon.borderStrokeWidth,
);
}
}

Expand Down
14 changes: 14 additions & 0 deletions lib/src/layer/polygon_layer/polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ class Polygon<R extends Object> {
final List<List<LatLng>>? holePointsList;

/// The fill color of the [Polygon].
///
/// Note that translucent (opacity is not 1 or 0) colors will reduce
/// performance, as the internal canvas must be drawn to and 'saved' more
/// frequently to ensure the colors of overlapping polygons are mixed
/// correctly.
final Color? color;

/// The stroke width of the [Polygon] outline.
Expand Down Expand Up @@ -69,6 +74,11 @@ class Polygon<R extends Object> {
final StrokeJoin strokeJoin;

/// The optional label of the [Polygon].
///
/// Note that specifying a label will reduce performance, as the internal
/// canvas must be drawn to and 'saved' more frequently to ensure the proper
/// stacking order is maintained. This can be avoided, potentially at the
/// expense of appearance, by setting [PolygonLayer.drawLabelsLast].
final String? label;

/// The [TextStyle] of the [Polygon.label].
Expand All @@ -78,6 +88,8 @@ class Polygon<R extends Object> {
///
/// [PolygonLabelPlacement.polylabel] can be expensive for some polygons. If
/// there is a large lag spike, try using [PolygonLabelPlacement.centroid].
///
/// Labels will not be drawn if there is not enough space.
final PolygonLabelPlacement labelPlacement;

/// Whether to rotate the label counter to the camera's rotation, to ensure
Expand Down Expand Up @@ -187,6 +199,8 @@ class Polygon<R extends Object> {
int? _renderHashCode;

/// An optimized hash code dedicated to be used inside the [_PolygonPainter].
///
/// Note that opacity is handled in the painter.
int get renderHashCode => _renderHashCode ??= Object.hash(
color,
borderStrokeWidth,
Expand Down
Loading