Skip to content

Commit ac26dde

Browse files
authored
Merge branch 'master' into caching
2 parents 8497204 + 916ba0f commit ac26dde

File tree

8 files changed

+294
-45
lines changed

8 files changed

+294
-45
lines changed

example/lib/pages/many_circles.dart

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'package:flutter_map_example/widgets/number_of_items_slider.dart';
99
import 'package:flutter_map_example/widgets/show_no_web_perf_overlay_snackbar.dart';
1010
import 'package:latlong2/latlong.dart';
1111

12-
const _maxCirclesCount = 20000;
12+
const _maxCirclesCount = 30000;
1313

1414
/// On this page, [_maxCirclesCount] circles are randomly generated
1515
/// across europe, and then you can limit them with a slider
@@ -21,15 +21,19 @@ class ManyCirclesPage extends StatefulWidget {
2121
const ManyCirclesPage({super.key});
2222

2323
@override
24-
ManyCirclesPageState createState() => ManyCirclesPageState();
24+
State<ManyCirclesPage> createState() => _ManyCirclesPageState();
2525
}
2626

27-
class ManyCirclesPageState extends State<ManyCirclesPage> {
28-
double doubleInRange(Random source, num start, num end) =>
27+
class _ManyCirclesPageState extends State<ManyCirclesPage> {
28+
static double doubleInRange(Random source, num start, num end) =>
2929
source.nextDouble() * (end - start) + start;
30-
List<CircleMarker> allCircles = [];
3130

3231
int numOfCircles = _maxCirclesCount ~/ 10;
32+
List<CircleMarker> allCircles = [];
33+
34+
bool useBorders = false;
35+
bool useRadiusInMeters = false;
36+
bool optimizeRadiusInMeters = true;
3337

3438
@override
3539
void initState() {
@@ -39,12 +43,16 @@ class ManyCirclesPageState extends State<ManyCirclesPage> {
3943

4044
Future.microtask(() {
4145
final r = Random();
42-
for (var x = 0; x < _maxCirclesCount; x++) {
46+
for (double x = 0; x < _maxCirclesCount; x++) {
4347
allCircles.add(
4448
CircleMarker(
4549
point: LatLng(doubleInRange(r, 37, 55), doubleInRange(r, -9, 30)),
46-
color: Colors.red,
50+
color: HSLColor.fromAHSL(1, x % 360, 1, doubleInRange(r, 0.3, 0.7))
51+
.toColor(),
4752
radius: 5,
53+
useRadiusInMeter: false,
54+
borderStrokeWidth: 0,
55+
borderColor: Colors.black,
4856
),
4957
);
5058
}
@@ -76,18 +84,106 @@ class ManyCirclesPageState extends State<ManyCirclesPage> {
7684
),
7785
children: [
7886
openStreetMapTileLayer,
79-
CircleLayer(circles: allCircles.take(numOfCircles).toList()),
87+
CircleLayer(
88+
circles: allCircles.take(numOfCircles).toList(growable: false),
89+
optimizeRadiusInMeters: optimizeRadiusInMeters,
90+
),
8091
],
8192
),
8293
Positioned(
8394
left: 16,
8495
top: 16,
8596
right: 16,
86-
child: NumberOfItemsSlider(
87-
number: numOfCircles,
88-
onChanged: (v) => setState(() => numOfCircles = v),
89-
maxNumber: _maxCirclesCount,
90-
itemDescription: 'Circle',
97+
child: RepaintBoundary(
98+
child: Column(
99+
children: [
100+
NumberOfItemsSlider(
101+
number: numOfCircles,
102+
onChanged: (v) => setState(() => numOfCircles = v),
103+
maxNumber: _maxCirclesCount,
104+
itemDescription: 'Circle',
105+
),
106+
const SizedBox(height: 12),
107+
UnconstrainedBox(
108+
child: Container(
109+
decoration: BoxDecoration(
110+
color: Theme.of(context).colorScheme.surface,
111+
borderRadius: BorderRadius.circular(32),
112+
),
113+
padding: const EdgeInsets.symmetric(
114+
vertical: 4,
115+
horizontal: 16,
116+
),
117+
child: Row(
118+
children: [
119+
const Tooltip(
120+
message: 'Use Borders',
121+
child: Icon(Icons.circle_outlined),
122+
),
123+
const SizedBox(width: 8),
124+
Switch.adaptive(
125+
value: useBorders,
126+
onChanged: (v) {
127+
allCircles = allCircles
128+
.map(
129+
(c) => CircleMarker(
130+
point: c.point,
131+
radius: c.radius,
132+
color: c.color,
133+
useRadiusInMeter: c.useRadiusInMeter,
134+
borderColor: c.borderColor,
135+
borderStrokeWidth: v ? 5 : 0,
136+
),
137+
)
138+
.toList(growable: false);
139+
useBorders = v;
140+
setState(() {});
141+
},
142+
),
143+
const SizedBox(width: 16),
144+
const Tooltip(
145+
message: 'Use Radius In Meters',
146+
child: Icon(Icons.straighten),
147+
),
148+
const SizedBox(width: 8),
149+
Switch.adaptive(
150+
value: useRadiusInMeters,
151+
onChanged: (v) {
152+
allCircles = allCircles
153+
.map(
154+
(c) => CircleMarker(
155+
point: c.point,
156+
radius: v ? 25000 : 5,
157+
color: c.color,
158+
useRadiusInMeter: v,
159+
borderColor: c.borderColor,
160+
borderStrokeWidth: c.borderStrokeWidth,
161+
),
162+
)
163+
.toList(growable: false);
164+
useRadiusInMeters = v;
165+
setState(() {});
166+
},
167+
),
168+
const SizedBox(width: 16),
169+
const Tooltip(
170+
message: 'Optimise Meters Radius',
171+
child: Icon(Icons.speed_rounded),
172+
),
173+
const SizedBox(width: 8),
174+
Switch.adaptive(
175+
value: optimizeRadiusInMeters,
176+
onChanged: useRadiusInMeters
177+
? (v) =>
178+
setState(() => optimizeRadiusInMeters = v)
179+
: null,
180+
),
181+
],
182+
),
183+
),
184+
),
185+
],
186+
),
91187
),
92188
),
93189
if (!kIsWeb)

example/lib/pages/polygon.dart

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ class _PolygonPageState extends State<PolygonPage> {
278278
],
279279
color: const Color(0xFFFF0000),
280280
hitValue: (
281-
title: 'Red Line',
282-
subtitle: 'Across the universe...',
281+
title: 'Big Red Diamond',
282+
subtitle: 'Across the anti-meridian boundary',
283283
),
284284
),
285285
];
@@ -295,8 +295,8 @@ class _PolygonPageState extends State<PolygonPage> {
295295
children: [
296296
FlutterMap(
297297
options: const MapOptions(
298-
initialCenter: LatLng(51.5, -2),
299-
initialZoom: 5,
298+
initialCenter: LatLng(51.5, -7),
299+
initialZoom: 4,
300300
),
301301
children: [
302302
openStreetMapTileLayer,
@@ -357,6 +357,17 @@ class _PolygonPageState extends State<PolygonPage> {
357357
simplificationTolerance: 0,
358358
useAltRendering: true,
359359
polygons: [
360+
Polygon(
361+
points: const [
362+
LatLng(51.5, -122),
363+
LatLng(50, -123),
364+
LatLng(50, -121),
365+
],
366+
borderStrokeWidth: 3,
367+
borderColor: Colors.red,
368+
color: Colors.orange,
369+
label: 'Culling tester',
370+
),
360371
Polygon(
361372
points: const [
362373
LatLng(50, -18),

lib/src/geo/latlng_bounds.dart

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ class LatLngBounds {
3030
/// The longitude west edge of the bounds
3131
double west;
3232

33+
/// Longitude center.
34+
final double longitudeCenter;
35+
36+
/// Longitude width. Can be more than one world, e.g. more than 360.
37+
final double longitudeWidth;
38+
3339
/// Create new [LatLngBounds] by providing two corners. Both corners have to
3440
/// be on opposite sites but it doesn't matter which opposite corners or in
3541
/// what order the corners are provided.
@@ -63,6 +69,33 @@ class LatLngBounds {
6369
);
6470
}
6571

72+
/// Creates bounds that can be larger than the world, longitude-wise.
73+
LatLngBounds.worldSafe({
74+
required this.north,
75+
required this.south,
76+
required this.longitudeCenter,
77+
required this.longitudeWidth,
78+
}) : assert(north <= maxLatitude,
79+
"The north latitude can't be bigger than $maxLatitude: $north"),
80+
assert(north >= minLatitude,
81+
"The north latitude can't be smaller than $minLatitude: $north"),
82+
assert(south <= maxLatitude,
83+
"The south latitude can't be bigger than $maxLatitude: $south"),
84+
assert(south >= minLatitude,
85+
"The south latitude can't be smaller than $minLatitude: $south"),
86+
assert(longitudeCenter <= maxLongitude,
87+
"The longitude center can't be bigger than $maxLongitude: $longitudeCenter"),
88+
assert(longitudeCenter >= minLongitude,
89+
"The longitude center can't be smaller than $minLongitude: $longitudeCenter"),
90+
assert(longitudeWidth >= 0, 'The longitude width must be positive'),
91+
assert(
92+
north >= south,
93+
"The north latitude ($north) can't be smaller than the "
94+
'south latitude ($south)',
95+
),
96+
east = min(longitudeCenter + longitudeWidth / 2, maxLongitude),
97+
west = max(longitudeCenter - longitudeWidth / 2, minLongitude);
98+
6699
/// Create a [LatLngBounds] instance from raw edge values.
67100
///
68101
/// Potentially throws assertion errors if the coordinates exceed their max
@@ -98,15 +131,52 @@ class LatLngBounds {
98131
east >= west,
99132
"The west longitude ($west) can't be smaller than the "
100133
'east longitude ($east)',
101-
);
134+
),
135+
longitudeCenter = (east + west) / 2,
136+
longitudeWidth = (east - west).abs();
102137

103138
/// Create a new [LatLngBounds] from a list of [LatLng] points. This
104139
/// calculates the bounding box of the provided points.
105-
factory LatLngBounds.fromPoints(List<LatLng> points) {
140+
factory LatLngBounds.fromPoints(
141+
List<LatLng> points, {
142+
bool drawInSingleWorld = false,
143+
}) {
106144
assert(
107145
points.isNotEmpty,
108146
'LatLngBounds cannot be created with an empty List of LatLng',
109147
);
148+
if (drawInSingleWorld) {
149+
const double halfWorld = 180;
150+
double previousLongitude = points.first.longitude;
151+
double minX = previousLongitude;
152+
double maxX = minX;
153+
double minY = maxLatitude;
154+
double maxY = minLatitude;
155+
for (final point in points) {
156+
double longitude = point.longitude;
157+
while (longitude - previousLongitude >= halfWorld) {
158+
longitude -= 2 * halfWorld;
159+
}
160+
while (longitude - previousLongitude <= -halfWorld) {
161+
longitude += 2 * halfWorld;
162+
}
163+
if (minX > longitude) {
164+
minX = longitude;
165+
}
166+
if (maxX < longitude) {
167+
maxX = longitude;
168+
}
169+
if (point.latitude < minY) minY = point.latitude;
170+
if (point.latitude > maxY) maxY = point.latitude;
171+
previousLongitude = longitude;
172+
}
173+
return LatLngBounds.worldSafe(
174+
north: maxY,
175+
south: minY,
176+
longitudeCenter: (maxX + minX) / 2,
177+
longitudeWidth: maxX - minX,
178+
);
179+
}
110180
// initialize bounds with max values.
111181
double minX = maxLongitude;
112182
double maxX = minLongitude;
@@ -200,7 +270,7 @@ class LatLngBounds {
200270
}
201271

202272
/// Obtain simple coordinates of the bounds center
203-
LatLng get simpleCenter => LatLng((south + north) / 2, (east + west) / 2);
273+
LatLng get simpleCenter => LatLng((south + north) / 2, longitudeCenter);
204274

205275
/// Checks whether [point] is inside bounds
206276
bool contains(LatLng point) =>
@@ -221,10 +291,24 @@ class LatLngBounds {
221291
///
222292
/// Bounding boxes that touch each other but don't overlap are counted as
223293
/// not overlapping.
224-
bool isOverlapping(LatLngBounds other) => !(south > other.north ||
225-
north < other.south ||
226-
east < other.west ||
227-
west > other.east);
294+
bool isOverlapping(LatLngBounds other) {
295+
if (south > other.north || north < other.south) {
296+
return false;
297+
}
298+
if (longitudeWidth >= 360 || other.longitudeWidth >= 360) {
299+
return true;
300+
}
301+
// TODO may not be relevant for projections without world replication
302+
var delta = longitudeCenter - other.longitudeCenter;
303+
while (delta >= 180) {
304+
delta -= 360;
305+
}
306+
while (delta <= -180) {
307+
delta += 360;
308+
}
309+
delta = delta.abs();
310+
return delta < longitudeWidth || delta < other.longitudeWidth;
311+
}
228312

229313
@override
230314
int get hashCode => Object.hash(south, north, east, west);
@@ -240,5 +324,7 @@ class LatLngBounds {
240324

241325
@override
242326
String toString() =>
243-
'LatLngBounds(north: $north, south: $south, east: $east, west: $west)';
327+
'LatLngBounds(north: $north, south: $south, east: $east, west: $west)'
328+
' (longitude center: $longitudeCenter)'
329+
' (longitude width: $longitudeWidth)';
244330
}

0 commit comments

Comments
 (0)