-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 998e6a7
Showing
10 changed files
with
297 additions
and
0 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,10 @@ | ||
# Files and directories created by pub. | ||
.dart_tool/ | ||
.packages | ||
|
||
# Conventional directory for build outputs. | ||
build/ | ||
|
||
# Omit committing pubspec.lock for library packages; see | ||
# https://dart.dev/guides/libraries/private-files#pubspeclock. | ||
pubspec.lock |
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,3 @@ | ||
## 1.0.0 | ||
|
||
- Initial version. |
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,11 @@ | ||
Copyright 2021 André Sousa | ||
|
||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | ||
|
||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | ||
|
||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
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,27 @@ | ||
<!-- | ||
This README describes the package. If you publish this package to pub.dev, | ||
this README's contents appear on the landing page for your package. | ||
For information about how to write a good package README, see the guide for | ||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages). | ||
For general information about developing packages, see the Dart guide for | ||
[creating packages](https://dart.dev/guides/libraries/create-library-packages) | ||
and the Flutter guide for | ||
[developing packages and plugins](https://flutter.dev/developing-packages). | ||
--> | ||
|
||
# polylabel | ||
|
||
Dart port of https://github.com/mapbox/polylabel. | ||
|
||
## Usage | ||
|
||
```dart | ||
import 'dart:math'; | ||
import 'package:polylabel/polylabel.dart'; | ||
final polygon = [[Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1), Point(0, 0)]]; | ||
final result = polylabel(polygon); // PolylabelResult{x: 0.5, y: 0.5, distance: 0.5} | ||
``` |
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,30 @@ | ||
# This file configures the static analysis results for your project (errors, | ||
# warnings, and lints). | ||
# | ||
# This enables the 'recommended' set of lints from `package:lints`. | ||
# This set helps identify many issues that may lead to problems when running | ||
# or consuming Dart code, and enforces writing Dart using a single, idiomatic | ||
# style and format. | ||
# | ||
# If you want a smaller set of lints you can change this to specify | ||
# 'package:lints/core.yaml'. These are just the most critical lints | ||
# (the recommended set includes the core lints). | ||
# The core lints are also what is used by pub.dev for scoring packages. | ||
|
||
include: package:lints/recommended.yaml | ||
|
||
# Uncomment the following section to specify additional rules. | ||
|
||
# linter: | ||
# rules: | ||
# - camel_case_types | ||
|
||
# analyzer: | ||
# exclude: | ||
# - path/to/excluded/files/** | ||
|
||
# For more information about the core and recommended set of lints, see | ||
# https://dart.dev/go/core-lints | ||
|
||
# For additional information about configuring this file, see | ||
# https://dart.dev/guides/language/analysis-options |
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,6 @@ | ||
import 'package:polylabel/polylabel.dart'; | ||
|
||
// void main() { | ||
// var awesome = Awesome(); | ||
// print('awesome: ${awesome.isAwesome}'); | ||
// } |
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,6 @@ | ||
/// Support for doing something awesome. | ||
/// | ||
/// More dartdocs go here. | ||
library polylabel; | ||
|
||
export 'src/polylabel_base.dart'; |
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,175 @@ | ||
import 'dart:math' show min, sqrt, sqrt2, Point; | ||
import 'package:collection/collection.dart'; | ||
|
||
typedef Polygon = List<List<Point>>; | ||
|
||
class Cell { | ||
final Point c; // cell center | ||
final num h; // half the cell size | ||
final num d; // distance from cell center to polygon | ||
late num max; // max distance to polygon within a cell | ||
|
||
Cell(this.c, this.h, Polygon polygon) : d = pointToPolygonDist(c, polygon) { | ||
max = d + h * sqrt2; | ||
} | ||
} | ||
|
||
class PolylabelResult { | ||
final num x; | ||
final num y; | ||
final num distance; | ||
PolylabelResult(this.x, this.y, this.distance); | ||
} | ||
|
||
/// Finds the polygon pole of inaccessibility, the most distant internal point | ||
/// from the polygon outline (not to be confused with centroid). | ||
/// | ||
/// Useful for optimal placement of a text label on a polygon. | ||
PolylabelResult polylabel( | ||
List<List<Point>> polygon, { | ||
double precision = 1.0, | ||
bool debug = false, | ||
}) { | ||
// find the bounding box of the outer ring | ||
num minX = 0, minY = 0, maxX = 0, maxY = 0; | ||
for (var i = 0; i < polygon[0].length; i++) { | ||
var p = polygon[0][i]; | ||
if (i == 0 || p.x < minX) minX = p.x; | ||
if (i == 0 || p.y < minY) minY = p.y; | ||
if (i == 0 || p.x > maxX) maxX = p.x; | ||
if (i == 0 || p.y > maxY) maxY = p.y; | ||
} | ||
|
||
num width = maxX - minX; | ||
num height = maxY - minY; | ||
num cellSize = min(width, height); | ||
num h = cellSize / 2; | ||
|
||
if (cellSize == 0) { | ||
return PolylabelResult(minX, minY, 0); | ||
} | ||
|
||
// a priority queue of cells in order of their "potential" (max distance to polygon) | ||
final cellQueue = PriorityQueue<Cell>(compareMax); | ||
|
||
// cover polygon with initial cells | ||
for (var x = minX; x < maxX; x += cellSize) { | ||
for (var y = minY; y < maxY; y += cellSize) { | ||
cellQueue.add(Cell(Point(x + h, y + h), h, polygon)); | ||
} | ||
} | ||
|
||
// take centroid as the first best guess | ||
var bestCell = getCentroidCell(polygon); | ||
|
||
// second guess: bounding box centroid | ||
var bboxCell = Cell(Point(minX + width / 2, minY + height / 2), 0, polygon); | ||
if (bboxCell.d > bestCell.d) bestCell = bboxCell; | ||
|
||
int numProbes = cellQueue.length; | ||
|
||
while (cellQueue.isNotEmpty) { | ||
// pick the most promising cell from the queue | ||
final cell = cellQueue.removeFirst(); | ||
|
||
// update the best cell if we found a better one | ||
if (cell.d > bestCell.d) { | ||
bestCell = cell; | ||
if (debug) { | ||
print( | ||
'found best ${(1e4 * cell.d).round() / 1e4} after $numProbes probes', | ||
); | ||
} | ||
} | ||
|
||
// do not drill down further if there's no chance of a better solution | ||
if (cell.max - bestCell.d <= precision) continue; | ||
|
||
// split the cell into four cells | ||
h = cell.h / 2; | ||
cellQueue.add(Cell(Point(cell.c.x - h, cell.c.y - h), h, polygon)); | ||
cellQueue.add(Cell(Point(cell.c.x + h, cell.c.y - h), h, polygon)); | ||
cellQueue.add(Cell(Point(cell.c.x - h, cell.c.y + h), h, polygon)); | ||
cellQueue.add(Cell(Point(cell.c.x + h, cell.c.y + h), h, polygon)); | ||
numProbes += 4; | ||
} | ||
|
||
if (debug) { | ||
print('best distance: ${bestCell.d}'); | ||
} | ||
|
||
return PolylabelResult(bestCell.c.x, bestCell.c.y, bestCell.d); | ||
} | ||
|
||
/// Compare two cells | ||
int compareMax(Cell a, Cell b) { | ||
return (b.max - a.max).toInt(); | ||
} | ||
|
||
/// Signed distance from point to polygon outline (negative if point is outside) | ||
num pointToPolygonDist(Point point, Polygon polygon) { | ||
bool inside = false; | ||
num minDistSq = double.infinity; | ||
|
||
for (var k = 0; k < polygon.length; k++) { | ||
final ring = polygon[k]; | ||
|
||
for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) { | ||
final a = ring[i]; | ||
final b = ring[j]; | ||
|
||
if ((a.y > point.y != b.y > point.y) && | ||
(point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x)) { | ||
inside = !inside; | ||
} | ||
|
||
minDistSq = min(minDistSq, getSegDistSq(point, a, b)); | ||
} | ||
} | ||
|
||
return minDistSq == 0 ? 0 : (inside ? 1 : -1) * sqrt(minDistSq); | ||
} | ||
|
||
/// Get polygon centroid | ||
Cell getCentroidCell(Polygon polygon) { | ||
num area = 0; | ||
num x = 0; | ||
num y = 0; | ||
final ring = polygon[0]; | ||
|
||
for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) { | ||
final a = ring[i]; | ||
final b = ring[j]; | ||
final f = a.x * b.y - b.x * a.y; | ||
x += (a.x + b.x) * f; | ||
y += (a.y + b.y) * f; | ||
area += f * 3; | ||
} | ||
if (area == 0) return Cell(ring[0], 0, polygon); | ||
return Cell(Point(x / area, y / area), 0, polygon); | ||
} | ||
|
||
/// Get squared distance from a point to a segment | ||
num getSegDistSq(Point p, Point a, Point b) { | ||
num x = a.x; | ||
num y = a.y; | ||
num dx = b.x - x; | ||
num dy = b.y - y; | ||
|
||
if (dx != 0 || dy != 0) { | ||
final t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy); | ||
|
||
if (t > 1) { | ||
x = b.x; | ||
y = b.y; | ||
} else if (t > 0) { | ||
x += dx * t; | ||
y += dy * t; | ||
} | ||
} | ||
|
||
dx = p.x - x; | ||
dy = p.y - y; | ||
|
||
return dx * dx + dy * dy; | ||
} |
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,13 @@ | ||
name: polylabel | ||
description: Dart port of https://github.com/mapbox/polylabel. | ||
version: 0.1.0 | ||
repository: https://github.com/beroso/dart_polylabel | ||
|
||
environment: | ||
sdk: '>=2.14.4 <3.0.0' | ||
|
||
dev_dependencies: | ||
lints: ^1.0.0 | ||
test: ^1.16.0 | ||
dependencies: | ||
collection: ^1.15.0 |
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,16 @@ | ||
import 'package:polylabel/polylabel.dart'; | ||
import 'package:test/test.dart'; | ||
|
||
void main() { | ||
// group('A group of tests', () { | ||
// final awesome = Awesome(); | ||
|
||
// setUp(() { | ||
// // Additional setup goes here. | ||
// }); | ||
|
||
// test('First Test', () { | ||
// expect(awesome.isAwesome, isTrue); | ||
// }); | ||
// }); | ||
} |