Skip to content

Instantly share code, notes, and snippets.

@lukepighetti
Last active January 1, 2021 21:14
Show Gist options
  • Save lukepighetti/442fca7115c752b9a93b025fc04b4c18 to your computer and use it in GitHub Desktop.
Save lukepighetti/442fca7115c752b9a93b025fc04b4c18 to your computer and use it in GitHub Desktop.
https://github.com/synw/geojson/issues/33#issuecomment-753375534 ✓ Geofencing Search Extensions Loads GeoJSON file, gets bounding boxes, performs search
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
import 'package:flutter/foundation.dart';
import 'package:geojson/geojson.dart';
extension GeoJsonSearchX on GeoJson {
/// Given a list of polygons, find which one contains a given point.
///
/// If the point isn't within any of these polygons, return `null`.
Future<List<GeoJsonFeature<GeoJsonPolygon>>> geofenceSearch(
List<GeoJsonFeature<GeoJsonPolygon>> geofences,
GeoJsonPoint query,
) async {
final boundingBoxes = getBoundingBoxes(geofences);
final filteredGeofences = [
for (var box in boundingBoxes)
if (box.contains(query.geoPoint.latitude, query.geoPoint.longitude))
box.feature
];
return _geofencesContainingPointNaive(filteredGeofences, query);
}
/// Return all geofences that contain the point provided.
///
/// Naive implementation. The geofences should be filtered first using a method such
/// as searching bounding boxes first.
Future<List<GeoJsonFeature<GeoJsonPolygon>>> _geofencesContainingPointNaive(
List<GeoJsonFeature<GeoJsonPolygon>> geofences,
GeoJsonPoint query,
) async {
final futures = [
for (var geofence in geofences)
geofencePolygon(
polygon: geofence.geometry,
points: [query],
).then((results) {
/// Nothing found
if (results.isEmpty) return null;
/// Found a result
if (results.first.name == query.name) return geofence;
})
];
final unfilteredResults = await Future.wait(futures);
return unfilteredResults.where((e) => e != null).toList();
}
/// Given a set of geofence polygons, find all of their bounding boxes, and the index at which they were found.
List<GeoBoundingBox> getBoundingBoxes(
List<GeoJsonFeature<GeoJsonPolygon>> geofences) {
final boundingBoxes = <GeoBoundingBox>[];
for (var i = 0; i <= geofences.length - 1; i++) {
final geofence = geofences[i];
double maxLat;
double minLat;
double maxLong;
double minLong;
for (var geoSerie in geofence.geometry.geoSeries) {
for (var geoPoint in geoSerie.geoPoints) {
final lat = geoPoint.latitude;
final long = geoPoint.longitude;
/// Make sure they get seeded if they are null
maxLat ??= lat;
minLat ??= lat;
maxLong ??= long;
minLong ??= long;
/// Update values
if (maxLat < lat) maxLat = lat;
if (minLat > lat) minLat = lat;
if (maxLong < long) maxLong = long;
if (minLong > long) minLong = long;
}
}
boundingBoxes.add(GeoBoundingBox(
feature: geofence,
minLat: minLat,
maxLong: maxLong,
maxLat: maxLat,
minLong: minLong,
));
}
return boundingBoxes;
}
}
class GeoBoundingBox {
/// A geographical rectangle. Typically used as a bounding box for a polygon
/// for fast search of point-in-multiple-polygon.
GeoBoundingBox({
@required this.feature,
@required this.maxLat,
@required this.maxLong,
@required this.minLat,
@required this.minLong,
});
/// The polygon bounded by this bounding box
final GeoJsonFeature<GeoJsonPolygon> feature;
final double maxLat;
final double maxLong;
final double minLat;
final double minLong;
double get left => minLat;
double get top => maxLong;
double get right => maxLat;
double get bottom => minLong;
bool contains(double lat, double long) {
final containsLat = maxLat >= lat && minLat <= lat;
final containsLong = maxLong >= long && minLong <= long;
return containsLat && containsLong;
}
@override
String toString() => 'GeoRect($minLat,$minLong,$maxLat,$maxLong)';
}
import 'package:flutter_test/flutter_test.dart';
import 'package:geojson/geojson.dart';
import 'package:mh/features/geofencing/geofencing_extensions.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('Geofencing Search Extensions', () {
test('Loads GeoJSON file, gets bounding boxes, performs search', () async {
final geo = GeoJson();
/// Test loading the GeoJSON file
final features = await geo.featuresFromGeoJsonAsset(
'assets/geojson/geofencing-search-test.json');
expect(features.collection.length, equals(7));
expect(features.polygons.length, equals(2));
/// Test bounding boxes
final geofences = features.polygons;
final boundingBoxes = geo.getBoundingBoxes(geofences);
/// polygon-a
///
/// latitude
/// min: 44.7120980759138
/// max: 44.848567071337264
///
/// longitude
/// min: -68.92959594726562
/// max: -68.80325317382812
///
/// contains pointB & pointC
final polygonABox = boundingBoxes.findId('polygon-a');
expect(polygonABox.minLat, equals(44.7120980759138));
expect(polygonABox.maxLat, equals(44.848567071337264));
expect(polygonABox.minLong, equals(-68.92959594726562));
expect(polygonABox.maxLong, equals(-68.80325317382812));
/// polygon-b
///
/// latidude
/// min: 44.692576173626684
/// max: 44.84223815129917
///
/// longitude
/// min: -68.88908386230469
/// max: -68.66111755371094
///
/// contains pointC & pointD
final polygonBBox = boundingBoxes.findId('polygon-b');
expect(polygonBBox.minLat, equals(44.692576173626684));
expect(polygonBBox.maxLat, equals(44.84223815129917));
expect(polygonBBox.minLong, equals(-68.88908386230469));
expect(polygonBBox.maxLong, equals(-68.66111755371094));
/// Test geofencing search
/// not fenced
final pointA = features.findId<GeoJsonPoint>('point-a');
final resultA = await geo.geofenceSearch(geofences, pointA.geometry);
expect(resultA.isEmpty, isTrue);
/// fenced by polygon-a
final pointB = features.findId<GeoJsonPoint>('point-b');
final resultB = await geo.geofenceSearch(geofences, pointB.geometry);
expect(resultB.length, equals(1));
expect(resultB.first.id, equals('polygon-a'));
/// fenced by polygon-a & polygon-b
final pointC = features.findId<GeoJsonPoint>('point-c');
final resultC = await geo.geofenceSearch(geofences, pointC.geometry);
expect(resultC.length, equals(2));
expect(resultC.first.id, equals('polygon-a'));
expect(resultC[1].id, equals('polygon-b'));
/// fenced by polygon-b
final pointD = features.findId<GeoJsonPoint>('point-d');
final resultD = await geo.geofenceSearch(geofences, pointD.geometry);
expect(resultD.length, equals(1));
expect(resultD.first.id, equals('polygon-b'));
/// within polygon-b bounding box, but not fenced by any polygons
final pointE = features.findId<GeoJsonPoint>('point-e');
final resultE = await geo.geofenceSearch(geofences, pointE.geometry);
expect(resultE.isEmpty, isTrue);
});
});
}
/// Convenience extension specifically for `geofence-search-test.json`
extension on GeoJsonFeatureCollection {
GeoJsonFeature<T> findId<T>(String id) =>
collection.firstWhere((e) => e.id == id);
}
/// Convenience extension specifically for `geofence-search-test.json`
extension on List<GeoBoundingBox> {
GeoBoundingBox findId(String id) => firstWhere((e) => e.feature.id == id);
}
/// Convenience extension specifically for `geofence-search-test.json`
extension on GeoJsonFeature {
String get id => properties['id'];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment