Flutter几何计算插件s2geometry_dart的使用
Flutter几何计算插件s2geometry_dart的使用
简介
S2 Geometry是由Google开发的强大库,它提供了一个在球面上处理几何数据的框架。S2 Geometry的核心思想是将地球表面投影到一个球体上,并将其划分为称为S2单元格的分层网格。这种系统使得高效的索引、查询和空间操作成为可能,如搜索附近的位置、面积计算等。S2单元格通过递归地将立方体的面投影到球体上来创建,它们在不同尺度上保持精度,从全球到非常精细的细节。
Google的S2 Geometry库被广泛应用于大规模应用中,包括Google地图,并且被广泛用于解决复杂的地理空间问题。
了解更多:
- 官方网站: http://s2geometry.io/
- Aidens博客: https://squarerfive.com/2023/11/27/why-s2/
- Christian S. Perone的博客: https://blog.christianperone.com/2015/08/googles-s2-geometry-on-the-sphere-cells-and-hilbert-curve/
参考文献
- JavaScript版本: https://www.npmjs.com/package/s2-geometry
- Python版本: https://github.com/google/s2geometry
- Java版本: https://github.com/google/s2-geometry-library-java
- Dart版本: https://github.com/nbspou/dart-s2geometry
移植计划
-
目标
- 功能完整性:Dart版本应能够实现JavaScript版本的所有功能。
- 使用方便性:提高使用的便利性,确保用户无需深入了解内部实现即可轻松使用该包。
-
准备与分析
- 由于JavaScript版本的所有函数都组合在一个脚本中,因此将其拆分为多个函数以提高可维护性。
- 为每个函数编写测试用例,确保每个函数在移植后仍然可用。
-
移植过程
- 分模块移植:
- 按照JavaScript版本的模块结构逐步移植模块。
- 为每个模块编写测试用例,确保移植完成后模块可用。
- 在移植每个函数后进行集成测试,以确保新模块不会破坏现有功能。
- 集成测试:
- 在移植每个函数后进行集成测试,以确保新模块不会破坏现有功能。
- 分模块移植:
-
测试
- 使用
test
包确保所有移植的功能按预期工作。每个函数都有对应的单元测试,所有函数移植完成后进行集成测试。
- 使用
任务难度
- 语言特性差异:JavaScript是一种动态类型语言,而Dart是一种静态类型语言,因此需要检查并重构每个变量和函数的类型。
- 数据结构:JavaScript中常用的
Array
和Object
在Dart中分别对应List
和Map
,需要重写代码以适应Dart的方法和特性。 - **使用
BigInt
声明S2单元格ID以解决超过64位数字的问题。
用户可用的功能
以下功能使用户能够执行地理计算和转换:
latLngToXYZ(LatLng latLng) // 将'lat', 'lng'转换为s2点
fromLatLng(LatLng latLng, int level) // 将'lat', 'lng'转换为s2单元格
latLngToId(lat, lng, [level]) // 直接将'lat', 'lng', 'level'转换为S2单元格ID。'level'默认为15,例如latLngToId(lat, lng, level = 10)来指定'level'。
latLngToKey(lat, lng, level) // 将'lat', 'lng', 'level'转换为S2单元格键。
latLngToNeighborKeys(lat, lng, level) // 将'lat', 'lng'转换为相邻的希尔伯特四叉树键。
getLatLng() // 将s2单元格转换为'lat', 'lng',这是一个非静态方法,因此需要创建S2Cell实例。
getCornerLatLngs() // 获取s2单元格的4个角点的'lat', 'lng',这是一个非静态方法,因此需要创建S2Cell实例。
getNeighbors() // 获取4个相邻的S2单元格,这是一个非静态方法,因此需要创建S2Cell实例。
keyToLatLng(key) // 将S2单元格键转换回'lat', 'lng'。
fromHilbertQuadKey(key) // 将键转换为s2单元格。
keyToId(key) // 将S2单元格键转换为S2单元格ID。
idToLatLng(id) // 将S2单元格ID转换回'lat', 'lng'。
idToKey(id) // 将S2单元格ID转换为键。
latLngToNeighborKeys(lat, lng, level) // 获取给定'lat'和'lng'在指定级别下的S2单元格邻居键列表。
nextKey(key) // 获取当前键之后的下一个S2单元格键。
prevKey(key) // 获取当前键之前的上一个S2单元格键。
stepKey(key, step) // 将当前键向前或向后移动指定步数。
facePosLevelToId(face, posS, levelN) // 获取S2单元格的面和四叉树列表,请参阅示例演示。
getFaceAndQuads() // 获取S2单元格的面和四叉树列表,请参阅示例演示。
toHilbertQuadkey() // 将S2单元格转换为希尔伯特四叉树键,请参阅示例演示。
使用说明
在pubspec.yaml
文件中添加依赖:
dependencies:
s2geometry_dart: ^0.0.3
示例
以下是一个完整的示例代码,展示了如何使用s2geometry_dart
插件进行地理计算和转换:
import 'package:s2geometry_dart/s2geometry_dart.dart';
void main() {
/// 定义纬度、经度和级别
var lat = 40.2574448;
var lng = -111.7089464;
var latlng = LatLng(lat, lng);
var level = 15;
/// 将纬度、经度转换为s2点
var point = S2.latLngToXYZ(latlng);
print('point: $point');
/// 将纬度、经度转换为s2单元格
var s2cellFromLatLng = S2.fromLatLng(latlng, level);
print('s2cellFromLatLng: $s2cellFromLatLng');
/// 将纬度、经度、级别转换为ID
var idFromLatLng = S2.latLngToId(lat, lng); // 级别默认为15,使用'level = 10'来指定级别,例如latLngToId(lat, lng, level = 10)
print('idFromLatLng: $idFromLatLng');
/// 将纬度、经度、级别转换为键
var key = S2.latLngToKey(lat, lng, level);
print('key: $key');
/// 将纬度、经度转换为相邻的希尔伯特四叉树键
var neighborsKeysFromLatLng = S2.latLngToNeighborKeys(lat, lng, level);
print('neighborsKeysFromLatLng: $neighborsKeysFromLatLng');
/// 将s2单元格转换为纬度、经度
var latLngFromS2Cell = s2cellFromLatLng.getLatLng(); /// getLatLng() 是非静态方法,所以需要创建S2Cell实例
print('latLngFromS2Cell: $latLngFromS2Cell');
/// 获取s2单元格的4个角点的纬度、经度
var corners = s2cellFromLatLng.getCornerLatLngs(); /// getCornerLatLngs() 是非静态方法,所以需要创建S2Cell实例
print('corners: $corners');
/// 获取4个相邻的S2单元格
var neighborsFromS2Cell = s2cellFromLatLng.getNeighbors(); /// getNeighbors() 是非静态方法,所以需要创建S2Cell实例
print('neighborsFromS2Cell: $neighborsFromS2Cell');
/// 将键转换为纬度、经度、级别
var latLngFromKey = S2.keyToLatLng(key);
print('latLngFromKey: $latLngFromKey');
/// 将键转换为s2单元格
var s2cellFromKey = S2.fromHilbertQuadKey(key);
print('s2cellFromKey: $s2cellFromKey');
/// 将键转换为ID
var idFromKey = S2.keyToId(key);
print('idFromKey: $idFromKey');
/// 将ID转换为纬度、经度、级别
var idToLatLng = S2.idToLatLng(idFromKey);
print('idToLatLng: $idToLatLng');
/// 将ID转换为键
var keyFromId = S2.idToKey(idFromKey);
print('keyFromId: $keyFromId');
/// 基于纬度、经度、级别获取邻居
var neighbors = S2.latLngToNeighborKeys(lat, lng, level);
print('neighbors: $neighbors');
/// 当前键的下一个键
var nextKey = S2.nextKey(key);
print('nextKey: $nextKey');
/// 当前键的前一个键
var prevKey = S2.prevKey(key);
print('prevKey: $prevKey');
/// 向后移动十个键
var backTenKeys = S2.stepKey(key, -10);
print('backTenKeys: $backTenKeys');
/// 将面、位置和级别转换为S2单元格ID
var quadkey = '4/0010023000';
var parts = quadkey.split('/');
var face = int.parse(parts[0]);
var posS = parts[1];
var levelN = posS.length;
var idFromFacePosLevel = S2.facePosLevelToId(face, posS, levelN);
print('idFromFacePosLevel: $idFromFacePosLevel');
/// 获取S2单元格的面和四叉树列表
S2 cell = S2.fromLatLng(latlng, level); // cell = [4, [1107, 8497], 15] 面,ij ,级别
var faceAndQuads = cell.getFaceAndQuads();
print('faceAndQuads: $faceAndQuads');
/// 将S2单元格转换为希尔伯特四叉树键
var hilbertQuadkey = cell.toHilbertQuadkey();
print('hilbertQuadkey: $hilbertQuadkey');
}
更多关于Flutter几何计算插件s2geometry_dart的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter几何计算插件s2geometry_dart的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
s2geometry_dart
是一个用于 Flutter 的 Dart 插件,它是 Google S2 Geometry Library 的 Dart 移植版本。S2 Geometry 是一个用于处理地理空间数据的库,特别适用于处理球面上的几何计算,比如计算两个地理位置之间的距离、判断一个点是否在多边形内、生成地理空间的索引等。
安装
在你的 pubspec.yaml
文件中添加 s2geometry_dart
依赖:
dependencies:
s2geometry_dart: ^1.0.0
然后运行 flutter pub get
来安装依赖。
基本使用
1. 创建 S2CellId
S2CellId
是 S2 中的基本单元,代表地球上的一个区域。你可以通过经纬度来创建一个 S2CellId
。
import 'package:s2geometry_dart/s2geometry_dart.dart';
void main() {
// 创建一个 S2CellId,使用经纬度
var s2CellId = S2CellId.fromLatLng(LatLng(37.4219999, -122.0840575));
print('S2CellId: ${s2CellId.id}');
}
2. 计算两个点之间的距离
你可以使用 S2LatLng
来计算两个经纬度点之间的距离。
import 'package:s2geometry_dart/s2geometry_dart.dart';
void main() {
var point1 = LatLng(37.4219999, -122.0840575); // 谷歌总部
var point2 = LatLng(34.052235, -118.243683); // 洛杉矶市中心
var distance = S2LatLng.fromDegrees(point1.latitude, point1.longitude)
.getDistance(S2LatLng.fromDegrees(point2.latitude, point2.longitude));
print('Distance: ${distance * 1000} meters'); // 输出距离(单位:米)
}
3. 判断点是否在多边形内
你可以使用 S2Polygon
来判断一个点是否在多边形内。
import 'package:s2geometry_dart/s2geometry_dart.dart';
void main() {
// 创建一个多边形
var polygon = S2Polygon([
LatLng(37.4219999, -122.0840575),
LatLng(37.4219999, -122.0840576),
LatLng(37.4219998, -122.0840576),
LatLng(37.4219998, -122.0840575),
]);
// 要判断的点
var point = LatLng(37.4219999, -122.0840575);
// 判断点是否在多边形内
var isInside = polygon.contains(point);
print('Is point inside polygon? $isInside');
}
4. 生成 S2CellId 的覆盖区域
你可以使用 S2RegionCoverer
来生成一组 S2CellId
,这些 S2CellId
可以覆盖指定的区域。
import 'package:s2geometry_dart/s2geometry_dart.dart';
void main() {
// 创建一个 S2RegionCoverer
var coverer = S2RegionCoverer();
// 设置最大和最小层级
coverer.maxCells = 10;
coverer.minLevel = 10;
coverer.maxLevel = 15;
// 创建一个矩形区域
var rect = S2LatLngRect.fromLatLng(S2LatLng.fromDegrees(37.4219999, -122.0840575),
S2LatLng.fromDegrees(37.4319999, -122.0940575));
// 生成覆盖区域的 S2CellId
var covering = coverer.getCovering(rect);
print('Covering cells: ${covering.length}');
}