Skip to content

Commit

Permalink
Merge bbox-polygon and bbox, center, polyline decode (#99)
Browse files Browse the repository at this point in the history
* Added coordEach translation

* Added bBox translation

* Added center translation

* Added decode points from polyline

* Update read me

* Remove and exclude .idea from git indexing

* Convert double to Position

* Use existing coord each implementation

* Return bBox points in the correct order

* Add bbox unit test

* Change libraries namespaces

* Code refactor

* Add doc blocs and some more missing functions

* Add encode polyline feature

* Add tests for both encode and decode polylines

* Add bbox tests

* Add helper functions

* Convert array to Bbox

* Remove redundant helper functions

* Add documentation

* implementation

* tests init, documentation, addition of types and debug

* implemented, docs

* test initiation

* added it to /lib, updated readme, can close 97

* renamed the test file

* comments resolved

* typo

* unused lib removed

* merge bbox-polygon

* missing center_test.dart

* imported bbox_polygon to center_test

* test error fixes

Co-authored-by: Dennis Mwea <[email protected]>
Co-authored-by: Lukas Himsel <[email protected]>
  • Loading branch information
3 people authored Jun 19, 2022
1 parent 560d2a8 commit 25c99d2
Show file tree
Hide file tree
Showing 31 changed files with 1,748 additions and 5 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ build/
# Directory created by dartdoc
doc/api/

coverage/
coverage/

.idea/
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] along
- [ ] area
- [ ] bbox
- [ ] bboxPolygon
- [x] [bboxPolygon] (https://github.com/dartclub/turf_dart/blob/main/lib/bbox_polygon.dart)
- [x] [bearing](https://github.com/dartclub/turf_dart/blob/main/lib/bearing.dart)
- [ ] center
- [ ] [center](https://github.com/Dennis-Mwea/turf_dart/blob/main/lib/src/center.dart)
- [ ] centerOfMass
- [ ] centroid
- [x] [destination](https://github.com/dartclub/turf_dart/blob/main/lib/destination.dart)
Expand Down Expand Up @@ -122,6 +122,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] transformScale
- [ ] union
- [ ] voronoi
- [x] [polyLineDecode](https://github.com/Dennis-Mwea/turf_dart/blob/main/lib/src/polyline.dart)

### Feature Conversion
- [ ] combine
Expand Down
3 changes: 3 additions & 0 deletions lib/bbox.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_bbox;

export "src/bbox.dart";
3 changes: 3 additions & 0 deletions lib/bbox_polygon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_bbox_polygon.dart;

export 'src/bbox_polygon.dart';
3 changes: 3 additions & 0 deletions lib/center.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_center;

export 'src/center.dart';
3 changes: 3 additions & 0 deletions lib/polyline.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_polyline;

export 'src/polyline.dart';
39 changes: 39 additions & 0 deletions lib/src/bbox.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:turf/helpers.dart';
import 'package:turf/meta.dart';

/// Calculates the bounding box for any [geoJson] object, including [FeatureCollection].
/// If [recompute] is not set and the [bbox] is not null, the function uses the [bbox] of the given [GeoJSONObject].
BBox bbox(GeoJSONObject geoJson, {bool recompute = false}) {
if (geoJson.bbox != null && !recompute) {
return geoJson.bbox!;
}

var result = BBox.named(
lat1: double.infinity,
lng1: double.infinity,
lat2: double.negativeInfinity,
lng2: double.negativeInfinity,
);

coordEach(
geoJson,
(Position? currentCoord, _, __, ___, ____) {
if (currentCoord != null) {
if (result.lng1 > currentCoord.lng) {
result = result.copyWith(lng1: currentCoord.lng);
}
if (result.lat1 > currentCoord.lat) {
result = result.copyWith(lat1: currentCoord.lat);
}
if (result.lng2 < currentCoord.lng) {
result = result.copyWith(lng2: currentCoord.lng);
}
if (result.lat2 < currentCoord.lat) {
result = result.copyWith(lat2: currentCoord.lat);
}
}
},
);

return result;
}
42 changes: 42 additions & 0 deletions lib/src/bbox_polygon.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:turf/helpers.dart';

/// Takes a [Bbox] and returns an equivalent [Feature<Polygon>].
/// ```dart
/// var bbox = Bbox(0, 0, 10, 10);
/// var poly = bboxPolygon(bbox);
/// //addToMap
/// var addToMap = [poly]
/// ```
Feature<Polygon> bboxPolygon(BBox bbox,
{Map<String, dynamic> properties = const {}, dynamic id}) {
var west = bbox[0]!;
var south = bbox[1]!;
var east = bbox[2]!;
var north = bbox[3]!;

if (bbox.length == 6) {
throw Exception("turf/bbox-polygon does not support BBox with 6 positions");
}

var lowLeft = [west, south];
var topLeft = [west, north];
var topRight = [east, north];
var lowRight = [east, south];

return Feature(
bbox: bbox.clone(),
properties: properties,
id: id,
geometry: Polygon(
coordinates: [
[
Position.of(lowLeft),
Position.of(lowRight),
Position.of(topRight),
Position.of(topLeft),
Position.of(lowLeft)
]
],
),
);
}
21 changes: 21 additions & 0 deletions lib/src/center.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:turf/bbox.dart' as t;
import 'package:turf/helpers.dart';

/// Takes a [Feature] or a [FeatureCollection] and returns the absolute center point of all feature(s).
Feature<Point> center(
GeoJSONObject geoJSON, {
dynamic id,
BBox? bbox,
Map<String, dynamic>? properties,
}) {
final BBox ext = t.bbox(geoJSON);
final num x = (ext[0]! + ext[2]!) / 2;
final num y = (ext[1]! + ext[3]!) / 2;

return Feature<Point>(
id: id,
bbox: bbox,
properties: properties,
geometry: Point(coordinates: Position.named(lat: y, lng: x)),
);
}
22 changes: 22 additions & 0 deletions lib/src/geojson.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:json_annotation/json_annotation.dart';

part 'geojson.g.dart';

@JsonEnum(alwaysCreate: true)
Expand Down Expand Up @@ -180,6 +181,7 @@ abstract class CoordinateType implements Iterable<num> {
CoordinateType toSigned();

bool get isSigned;

_untilSigned(val, limit) {
if (val > limit) {
return _untilSigned(val - 360, limit);
Expand Down Expand Up @@ -245,7 +247,9 @@ class Position extends CoordinateType {
Position operator *(Position p) => crossProduct(p);

num get lng => _items[0];

num get lat => _items[1];

num? get alt => length == 3 ? _items[2] : null;

@override
Expand Down Expand Up @@ -317,12 +321,30 @@ class BBox extends CoordinateType {
bool get _is3D => length == 6;

num get lng1 => _items[0];

num get lat1 => _items[1];

num? get alt1 => _is3D ? _items[2] : null;

num get lng2 => _items[_is3D ? 3 : 2];

num get lat2 => _items[_is3D ? 4 : 3];

num? get alt2 => _is3D ? _items[5] : null;

BBox copyWith({
num? lng1,
num? lat1,
num? lng2,
num? lat2,
}) =>
BBox(
lng1 ?? this.lng1,
lat1 ?? this.lat1,
lng2 ?? this.lng2,
lat2 ?? this.lat2,
);

@override
BBox clone() => BBox.of(_items);

Expand Down
100 changes: 100 additions & 0 deletions lib/src/polyline.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'dart:math' as math;

import 'package:turf/helpers.dart';

/// Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
/// Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
/// by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
class Polyline {
static String _encode(num current, num previous, num factor) {
current = _py2Round(current * factor);
previous = _py2Round(previous * factor);
var coordinate = (current - previous).toInt();
coordinate <<= 1;
if (current - previous < 0) {
coordinate = ~coordinate;
}
var output = '';
while (coordinate >= 0x20) {
output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
coordinate >>= 5;
}
output += String.fromCharCode(coordinate + 63);

return output;
}

static int _py2Round(num value) {
// Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
return (value.abs() + 0.5).floor() * (value >= 0 ? 1 : -1);
}

/// Decodes a Polyline to a [List<Position>].
/// This is adapted from the implementation in Project-OSRM.
/// See https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
static List<Position> decode(String polyline, {int precision = 5}) {
var index = 0,
lat = 0,
lng = 0,
shift = 0,
result = 0,
factor = math.pow(10, precision);
int? byte;
late int latitudeChange;
late int longitudeChange;
List<Position> coordinates = [];

// Coordinates have variable length when encoded, so just keep
// track of whether we've hit the end of the string. In each
// loop iteration, a single coordinate is decoded.
while (index < polyline.length) {
byte = null;
shift = 0;
result = 0;

do {
byte = polyline.codeUnitAt(index++) - 63;
result |= (byte & 0x1f) << shift;
shift += 5;
} while (byte >= 0x20);
latitudeChange = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
shift = result = 0;

do {
byte = polyline.codeUnitAt(index++) - 63;
result |= (byte & 0x1f) << shift;
shift += 5;
} while (byte >= 0x20);
longitudeChange = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));

lat += latitudeChange;
lng += longitudeChange;

coordinates.add(
Position.named(
lng: (lng / factor).toDouble(), lat: (lat / factor).toDouble()),
);
}

return coordinates;
}

/// Encodes the given [List<Position>] to a polyline, a [String].
static String encode(List<Position> coordinates, {int? precision}) {
if (coordinates.isEmpty) {
return '';
}

var factor = math.pow(10, precision ?? 5),
output = _encode(coordinates[0].lat, 0, factor) +
_encode(coordinates[0].lng, 0, factor);

for (var i = 1; i < coordinates.length; i++) {
var a = coordinates[i], b = coordinates[i - 1];
output += _encode(a.lat, b.lat, factor);
output += _encode(a.lng, b.lng, factor);
}

return output;
}
}
5 changes: 4 additions & 1 deletion lib/turf.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
library turf;

export 'src/bbox.dart';
export 'src/bearing.dart';
export 'src/center.dart';
export 'src/destination.dart';
export 'src/distance.dart';
export 'src/geojson.dart';
export 'src/midpoint.dart';
export 'src/helpers.dart';
export 'src/midpoint.dart';
export 'src/nearest_point.dart';
export 'src/polyline.dart';
export 'src/nearest_point_on_line.dart';
58 changes: 58 additions & 0 deletions test/components/bbox_polygon_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:test/test.dart';
import 'package:turf/helpers.dart';
import 'package:turf/src/bbox_polygon.dart';

main() {
test(
"bbox-polygon",
() {
var poly = bboxPolygon(BBox(0, 0, 10, 10));
expect(poly.geometry is Polygon, true);
},
);

test(
"bbox-polygon -- valid geojson",
() {
var poly = bboxPolygon(BBox(0, 0, 10, 10));
var coordinates = poly.geometry!.coordinates;

expect(coordinates[0].length == 5, true);
expect(coordinates[0][0][0] == coordinates[0][coordinates.length - 1][0],
true);
expect(coordinates[0][0][1] == coordinates[0][coordinates.length - 1][1],
true);
},
);

test(
"bbox-polygon -- Error handling",
() {
expect(() => bboxPolygon(BBox(-110, 70, 5000, 50, 60, 3000)),
throwsA(isA<Exception>()));
},
);

test(
"bbox-polygon -- Translate BBox (Issue #1179)",
() {
var id = 123;
var properties = {"foo": "bar"};
var bbox = BBox(0, 0, 10, 10);
var poly = bboxPolygon(bbox, properties: properties, id: id);

expect(poly.properties, equals(properties));
expect(poly.bbox, equals(bbox));
expect(poly.id, equals(id));
},
);

test(
"bbox-polygon -- assert bbox",
() {
var bbox = BBox(0, 0, 10, 10);
var poly = bboxPolygon(bbox);
expect(poly.bbox, equals(bbox));
},
);
}
Loading

0 comments on commit 25c99d2

Please sign in to comment.