Flutter地图功能插件mapplet的使用
Flutter地图功能插件mapplet的使用
mapplet
是一个旨在简化离线地图存储的 Flutter 插件。它基于 flutter_map
包,并提供了易于使用的层来自动显示用户当前位置。
特性
- 简单设计:
mapplet
的设计初衷是为了简化开发者的工作流程。 - 区域存储:每个
Depot
可以包含多个区域。 - 节省空间:
mapplet
自动检测并共享已存储的区域之间的图块,避免重复下载。 - 并行下载:区域图块通过多线程下载,并批量写入内部数据库。
- 快速学习曲线:
mapplet
仅暴露必要的 API,集成非常简单。 - 当前位置层:提供了一个简单的层来自动显示用户的当前位置。
定义
Region(区域)
- 定义:由
LatLngBounds
确定的区域。
Depot(仓库)
- 定义:标识可以包含任意数量
Region
的存储。仓库内的图块在不同区域之间动态共享,以防止重复下载并节省空间。
主要功能
- 存储和删除区域:每个
Depot
可以包含许多区域。 - 节省空间:
mapplet
检测并共享仓库内已存储区域之间的图块,避免重复下载。 - 并行下载:区域图块通过多线程下载,并批量写入内部数据库。
- 极速学习曲线:
mapplet
仅暴露必要的 API,集成非常简单。 - 当前位置层:提供了一个简单的层来自动显示用户的当前位置。
开始使用
初始化插件
首先,初始化插件并配置每个 Depot
。建议使用 path_provider
包来获取适应不同平台的目录位置。
await Mapplet.initiate([
DepotConfiguration(
id: "default_depot",
minZoom: 10,
maxZoom: 16,
directory: (await getApplicationDocumentsDirectory()).path,
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
),
]);
使用
存储区域
首先,创建一个描述要存储区域的 LatLngBounds
。
var center = LatLng(46, 12);
var bounds = LatLngBoundsExtensions.fromDelta(center, 10);
然后,检索 Depot
实例并运行下载操作。
var depot = await Mapplet.depot("default_depot");
var depositOp = await depot.depositRegion("region_id", bounds);
// 监听取消事件
depositOp.onAbort.listen((_) async {
// 处理取消操作
});
// 监听提交事件
depositOp.onCommit.listen((commitFuture) async {
// 处理提交操作
});
// 开始下载操作
var stream = depositOp.fetch();
// 等待进度报告
await for (final progress in stream) {
// 处理进度
// 或者随时取消操作
await depositOp.abort();
}
删除区域
var depot = await Mapplet.depot("default_depot");
await depot.dropRegion("region_id");
使用 TileProvider
var depot = await Mapplet.depot("default_depot");
var tileProvider = depot.getTileProvider();
额外信息
mapplet
在 extensions
模块中有一些有用的扩展方法,这些方法会自动导入。
当前位置层
mapplet
还包括一个有用的层来自动显示用户的当前位置。
LocationWatcherLayer(
positionStream: _positionStream.stream,
style: LocationWatcherLayerStyle(
showAccuracyCircle: false,
directionRadius: 64,
directionAngle: 80,
showDirection: widget.showDirectionSector,
markerSize: const Size.square(20),
directionColor: Colors.red,
locationMarker: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Padding(
padding: const EdgeInsets.all(2),
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
),
),
)
示例代码
以下是一个完整的示例代码,展示了如何使用 mapplet
插件。
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:mapplet/mapplet.dart';
import 'package:path_provider/path_provider.dart';
Future<void> main() async {
await Mapplet.initialize([
DepotConfiguration(
id: "default_depot",
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
directory: (await getApplicationDocumentsDirectory()).path,
minZoom: 10,
maxZoom: 16,
),
]);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: "Mapplet",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: "Mapplet"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Depot _depot = Mapplet.depot("default_depot");
DepotStats? _stats;
FetchOperation? _fetchOp;
FetchProgress? _progressFirst;
FetchProgress? _progressSecond;
bool _committingFirst = false;
bool _committingSecond = false;
Widget _buildStats() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
children: [
const Icon(Icons.storage_rounded),
Text(
_stats != null ? "${_stats!.byteSize.byteToMib().toStringAsFixed(1)} MiB" : "...",
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 4),
Text(
"occupied",
style: Theme.of(context).textTheme.bodySmall,
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
children: [
const Icon(Icons.landscape_rounded),
Text(
_stats != null ? _stats!.regionCount.toString() : "...",
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 4),
Text(
"regions",
style: Theme.of(context).textTheme.bodySmall,
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
children: [
const Icon(Icons.square_rounded),
Text(
_stats != null ? _stats!.tilesCount.toString() : "...",
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 4),
Text(
"tiles",
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
);
}
Iterable<Widget> _buildFirstRegion() sync* {
yield const Text(
"First region",
style: TextStyle(fontSize: 20),
);
yield Padding(
padding: const EdgeInsets.all(8),
child: LinearProgressIndicator(
value: _progressFirst?.progress ?? 0,
),
);
yield Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
height: 16,
width: 16,
child: _committingFirst ? const CircularProgressIndicator() : null,
),
);
yield Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton.icon(
onPressed: _committingSecond || _committingFirst
? null
: () async {
_fetchOp = _depot.depositRegion("region", LatLngBoundsExtensions.fromDelta(const LatLng(46, 12), 2.5));
_fetchOp!.onAbort.listen((_) {
debugPrint("operation aborted");
setState(() => _committingFirst = false);
});
_fetchOp!.onCommit.listen((commitFuture) async {
setState(() => _committingFirst = true);
await commitFuture;
debugPrint("operation committed");
var stats = await _depot.getStats();
setState(() {
_committingFirst = false;
_stats = stats;
});
});
await for (final progress in _fetchOp!.fetch()) {
setState(() => _progressFirst = progress);
}
},
icon: const Icon(Icons.download_rounded),
label: const Text("Download region"),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton.icon(
onPressed: _committingSecond || _committingFirst
? null
: () async {
await _depot.dropRegion("region");
var stats = await _depot.getStats();
setState(() => _stats = stats);
},
icon: const Icon(Icons.delete_rounded),
label: const Text("Delete region"),
),
),
),
],
);
}
Iterable<Widget> _buildSecondRegion() sync* {
yield const Text(
"Second region",
style: TextStyle(fontSize: 20),
);
yield Padding(
padding: const EdgeInsets.all(8),
child: LinearProgressIndicator(
value: _progressSecond?.progress ?? 0,
),
);
yield Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
height: 16,
width: 16,
child: _committingSecond ? const CircularProgressIndicator() : null,
),
);
yield Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton.icon(
onPressed: _committingSecond || _committingFirst
? null
: () async {
_fetchOp = _depot.depositRegion("region_1", LatLngBoundsExtensions.fromDelta(const LatLng(46, 12), 5));
_fetchOp!.onAbort.listen((_) {
debugPrint("operation aborted");
setState(() => _committingSecond = false);
});
_fetchOp!.onCommit.listen((commitFuture) async {
setState(() => _committingSecond = true);
await commitFuture;
debugPrint("operation committed");
var stats = await _depot.getStats();
setState(() {
_committingSecond = false;
_stats = stats;
});
});
await for (final progress in _fetchOp!.fetch()) {
setState(() => _progressSecond = progress);
}
},
icon: const Icon(Icons.download_rounded),
label: const Text("Download region"),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: ElevatedButton.icon(
onPressed: _committingSecond || _committingFirst
? null
: () async {
await _depot.dropRegion("region_1");
var stats = await _depot.getStats();
setState(() => _stats = stats);
},
icon: const Icon(Icons.delete_rounded),
label: const Text("Delete region"),
),
),
),
],
);
yield const Text(
"The second region is centered at the same spot of the first region but expands for double the area.\nMapplet detects the overlap and skips the fetch procedure on common tiles",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_buildStats(),
const Divider(height: 32),
..._buildFirstRegion(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 32),
child: Container(height: 8, color: Colors.grey),
),
..._buildSecondRegion(),
const Divider(),
],
),
),
),
);
}
}
更多关于Flutter地图功能插件mapplet的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter地图功能插件mapplet的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,mapplet
是一个用于集成地图功能的强大插件。它允许开发者在应用中嵌入交互式地图,支持多种功能如定位、标记、路线规划等。以下是一个使用 mapplet
插件的基本示例代码,展示了如何在 Flutter 应用中实现地图功能。
首先,确保在你的 pubspec.yaml
文件中添加 mapplet
依赖项:
dependencies:
flutter:
sdk: flutter
mapplet: ^最新版本号 # 请替换为实际可用的最新版本号
然后,运行 flutter pub get
来获取依赖项。
接下来,在你的 Flutter 项目中,创建一个显示地图的页面。以下是一个简单的示例代码:
import 'package:flutter/material.dart';
import 'package:mapplet/mapplet.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MapScreen(),
);
}
}
class MapScreen extends StatefulWidget {
@override
_MapScreenState createState() => _MapScreenState();
}
class _MapScreenState extends State<MapScreen> {
MappletController? _mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Mapplet Demo'),
),
body: MappletMap(
apiKey: '你的Mapplet API密钥', // 请替换为你的实际API密钥
onMapCreated: (MappletController mapController) {
_mapController = mapController;
// 可以在这里进行其他地图初始化操作,比如设置初始中心点
_mapController?.moveCamera(CameraUpdateFactory.newLatLng(LatLng(37.7749, -122.4194))); // 旧金山
},
myLocationEnabled: true,
myLocationButtonEnabled: true,
zoomGesturesEnabled: true,
scrollGesturesEnabled: true,
tiltGesturesEnabled: true,
rotateGesturesEnabled: true,
compassEnabled: true,
markers: [
MappletMarker(
position: LatLng(37.7749, -122.4194), // 旧金山
infoWindow: MappletInfoWindow(
title: '旧金山',
snippet: '美国加利福尼亚州的城市',
),
icon: MappletBitmapDescriptorFactory.defaultMarker(
color: Colors.red,
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_mapController != null) {
_mapController?.showMyLocation();
}
},
tooltip: '显示我的位置',
child: Icon(Icons.location_on),
),
);
}
}
关键点解释:
- 依赖项添加:在
pubspec.yaml
中添加mapplet
依赖项。 - API 密钥:在
MappletMap
组件中,你需要提供apiKey
,这是从 Mapplet 平台获取的 API 密钥。 - 地图控制器:通过
onMapCreated
回调获取MappletController
实例,用于控制地图的各种操作,如移动相机、添加标记等。 - 标记:在
markers
属性中,你可以添加多个MappletMarker
,每个标记都包含位置、信息窗口和图标。 - 功能按钮:通过
floatingActionButton
添加一个浮动操作按钮,用于显示当前位置。
这个示例代码展示了如何在 Flutter 应用中集成 mapplet
插件,并展示了基本的地图功能。你可以根据需求进一步扩展,如添加更多的标记、自定义图标、处理地图点击事件等。