forked from fleaflet/flutter_map
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf: expose
isPointInPolygon
and make 40% faster (at least in JIT …
…mode) (fleaflet#1907) Co-authored-by: Sebastian <[email protected]>
- Loading branch information
1 parent
b69a0d7
commit c321865
Showing
7 changed files
with
171 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import 'dart:async'; | ||
import 'dart:math' as math; | ||
import 'dart:ui'; | ||
|
||
import 'package:flutter_map/src/misc/point_in_polygon.dart'; | ||
import 'package:logger/logger.dart'; | ||
|
||
class NoFilter extends LogFilter { | ||
@override | ||
bool shouldLog(LogEvent event) => true; | ||
} | ||
|
||
typedef Result = ({ | ||
String name, | ||
Duration duration, | ||
}); | ||
|
||
Future<Result> timedRun(String name, dynamic Function() body) async { | ||
Logger().i('running $name...'); | ||
final watch = Stopwatch()..start(); | ||
await body(); | ||
watch.stop(); | ||
|
||
return (name: name, duration: watch.elapsed); | ||
} | ||
|
||
List<Offset> makeCircle(int points, double radius, double phase) { | ||
final slice = math.pi * 2 / (points - 1); | ||
return List.generate(points, (i) { | ||
// Note the modulo is only there to deal with floating point imprecision | ||
// and ensure first == last. | ||
final angle = slice * (i % (points - 1)) + phase; | ||
return Offset(radius * math.cos(angle), radius * math.sin(angle)); | ||
}, growable: false); | ||
} | ||
|
||
// NOTE: to have a more prod like comparison, run with: | ||
// $ dart compile exe benchmark/crs.dart && ./benchmark/crs.exe | ||
// | ||
// If you run in JIT mode, the resulting execution times will be a lot more similar. | ||
Future<void> main() async { | ||
Logger.level = Level.all; | ||
Logger.defaultFilter = NoFilter.new; | ||
Logger.defaultPrinter = SimplePrinter.new; | ||
|
||
final results = <Result>[]; | ||
const N = 3000000; | ||
|
||
final circle = makeCircle(1000, 1, 0); | ||
|
||
results.add(await timedRun('In circle', () { | ||
const point = math.Point(0, 0); | ||
|
||
bool yesPlease = true; | ||
for (int i = 0; i < N; ++i) { | ||
yesPlease = yesPlease && isPointInPolygon(point, circle); | ||
} | ||
|
||
assert(yesPlease, 'should be in circle'); | ||
return yesPlease; | ||
})); | ||
|
||
results.add(await timedRun('Not in circle', () { | ||
const point = math.Point(4, 4); | ||
|
||
bool noSir = false; | ||
for (int i = 0; i < N; ++i) { | ||
noSir = noSir || isPointInPolygon(point, circle); | ||
} | ||
|
||
assert(!noSir, 'should not be in circle'); | ||
return noSir; | ||
})); | ||
|
||
Logger().i('Results:\n${results.map((r) => r.toString()).join('\n')}'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import 'dart:math' as math; | ||
import 'dart:ui'; | ||
|
||
/// Checks whether point [p] is within the specified closed [polygon] | ||
/// | ||
/// Uses the even-odd algorithm and requires closed loop polygons, i.e. | ||
/// `polygon.first == polygon.last`. | ||
bool isPointInPolygon(math.Point p, List<Offset> polygon) { | ||
final len = polygon.length; | ||
assert(len >= 3, 'not a polygon'); | ||
assert(polygon.first == polygon.last, 'polygon not closed'); | ||
final double px = p.x.toDouble(); | ||
final double py = p.y.toDouble(); | ||
|
||
bool isInPolygon = false; | ||
for (int i = 0, j = len - 1; i < len; j = i++) { | ||
final double poIx = polygon[i].dx; | ||
final double poIy = polygon[i].dy; | ||
|
||
final double poJx = polygon[j].dx; | ||
final double poJy = polygon[j].dy; | ||
|
||
if ((((poIy <= py) && (py < poJy)) || ((poJy <= py) && (py < poIy))) && | ||
(px < (poJx - poIx) * (py - poIy) / (poJy - poIy) + poIx)) { | ||
isInPolygon = !isInPolygon; | ||
} | ||
} | ||
return isInPolygon; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import 'dart:math' as math; | ||
|
||
import 'package:flutter_map/src/misc/point_in_polygon.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
List<Offset> makeCircle(int points, double radius, double phase) { | ||
final slice = math.pi * 2 / (points - 1); | ||
return List.generate(points, (i) { | ||
// Note the modulo is only there to deal with floating point imprecision | ||
// and ensure first == last. | ||
final angle = slice * (i % (points - 1)) + phase; | ||
return Offset(radius * math.cos(angle), radius * math.sin(angle)); | ||
}, growable: false); | ||
} | ||
|
||
void main() { | ||
test('Smoke test for points in and out of polygons', () { | ||
final circle = makeCircle(100, 1, 0); | ||
|
||
// Inside points | ||
for (final point in makeCircle(32, 0.8, 0.0001)) { | ||
final p = math.Point(point.dx, point.dy); | ||
expect(isPointInPolygon(p, circle), isTrue); | ||
} | ||
|
||
// Edge-case: check origin | ||
expect(isPointInPolygon(const math.Point(0, 0), circle), isTrue); | ||
|
||
// Outside points: small radius | ||
for (final point in makeCircle(32, 1.1, 0.0001)) { | ||
final p = math.Point(point.dx, point.dy); | ||
expect(isPointInPolygon(p, circle), isFalse); | ||
} | ||
|
||
// Outside points: large radius | ||
for (final point in makeCircle(32, 100000, 0.0001)) { | ||
final p = math.Point(point.dx, point.dy); | ||
expect(isPointInPolygon(p, circle), isFalse); | ||
} | ||
}); | ||
} |