Flutter室内地图导航插件mapsindoors_mapbox的使用

发布于 1周前 作者 songsunli 来自 Flutter

Flutter室内地图导航插件mapsindoors_mapbox的使用

介绍

mapsindoors_mapbox 是一个用于在 Flutter 应用中集成原生 MapsIndoors SDK 的联邦插件。通过该插件,你可以实现室内地图显示、实时路径导航和位置实时更新等功能。

支持平台

平台 Android iOS
支持 SDK 21+ 14.0+

特性

使用此插件可以:

  • 显示室内地图和导航。
  • 实现实时路径导航。
  • 查看位置实时更新。

此插件基于 MapsIndoors V4 SDK for Android 和 iOS。

入门指南

添加依赖

在你的 pubspec.yaml 文件中添加 mapsindoors_mapbox 依赖:

dependencies:
  mapsindoors_mapbox: ^4.1.0

Android 配置

设置 Mapbox API Key

  1. 导航到 android/app/src/main/res/value
  2. 在此文件夹中创建一个名为 mapbox_api_key.xml 的文件
  3. 复制并粘贴以下代码片段,并将 YOUR_KEY_HERE 替换为你的 Mapbox API Key
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="mapbox_api_key" translatable="false">YOUR_KEY_HERE</string>
    <string name="mapbox_access_token" translatable="false">YOUR_KEY_HERE</string>
</resources>

MapsIndoors Gradle 配置

  1. 导航到应用的项目级别 build.gradle 文件
  2. allprojects/repositories 中添加以下内容,放在 mavenCentral() 之后
maven { url 'https://maven.mapsindoors.com/' }
maven {
    url 'https://api.mapbox.com/downloads/v2/releases/maven'
    authentication {
        basic(BasicAuthentication)
    }
    credentials {
        // 不要更改用户名,始终应为 `mapbox`(而不是你的用户名)
        username = "mapbox"
        // 使用你在 `gradle.properties` 中存储的秘密令牌作为密码
        password = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: ""
    }
}
  1. 将你的 Mapbox 下载令牌添加到顶级 gradle.properties 文件中
MAPBOX_DOWNLOADS_TOKEN=YOUR_KEY_HERE

iOS 配置

确保你的 Podfile 配置为 iOS 14,并在应用目标中添加 use_frameworks!

platform :ios, '14.0'

target 'MyApp' do
  use_frameworks!
  ...
end

提供 API Key

导航到你的应用设置,并将你的 Mapbox 公共访问令牌添加到 Info.plist 中,键为 MBXAccessToken。同时,设置你的秘密访问令牌以下载 SDK。详情请参阅 配置凭证

使用示例

显示地图

以下代码展示了如何在 Flutter 应用中设置 MapsIndoors。首先,将 MapsIndoorsWidget 添加到应用的构建树中。

import 'package:flutter/material.dart';
import 'package:mapsindoors/mapsindoors.dart' as MapsIndoors;
import 'package:mapsindoors/mapsindoors_library.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MapWidget("demo"),
    );
  }
}

class MapWidget extends StatefulWidget {
  final String apiKey;

  const MapWidget({Key? key, required this.apiKey}) : super(key: key);

  @override
  MapWidgetState createState() => MapWidgetState();
}

class MapWidgetState extends State<MapWidget> {
  final _floorSelectorWidget = MPDefaultFloorSelector();
  late final MapsIndoorsWidget _mapController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: _mapController = MapsIndoorsWidget(
          floorSelector: _floorSelectorWidget,
          initialCameraPosition: MPCameraPosition(zoom: 7, point: MPPoint.withCoordinates(longitude: -118.0165, latitude: 33.9457)),
          readyListener: _mapControlReadyListener,
        ),
      ),
    );
  }

  @override
  void initState() {
    super.initState();
    loadMapsIndoors(widget.apiKey);
  }

  void _mapControlReadyListener(MPError? error) async {
    if (error == null) {
      _mapController.goTo(await getDefaultVenue());
    } else {
      print("Creating mapcontrol faced an issue: $error");
    }
  }
}

显示路线

以下代码初始化了 MPDirectionsServiceMPDirectionsRenderer 类,并使用已初始化的 _mapControl 查询从用户当前位置到指定位置的路线。

late final _mapControl;

var _userLocation = MPPoint.withCoordinates(longitude: -98.44, latitude: 35.16);

void showRouteToLocation(MPLocation location) async {
  final directionsService = MPDirectionsService();
  final directionsRenderer = MPDirectionsRenderer();

  directionsService.getRoute(origin: _userLocation, destination: location.point).then((route) {
    directionsRenderer.setRoute(route);
  }).catchError((err) => print("An error occurred: $err"));
}

搜索地点

以下代码展示了如何搜索匹配查询字符串 “parking” 的地点。

void searchForParking(MPPoint point) {
  final mpq = MPQuery.builder()
    ..setQuery("parking")
    ..setNear(point)
    ..setQueryProperties([MPLocationPropertyNames.description.name, MPLocationPropertyNames.name.name, MPLocationPropertyNames.externalId.name]);

  getLocationsByQuery(query: mpq.build()).then((locations) {
    print("Number of parking spots near the point: ${locations?.length}");
    // 处理找到的地点
  });
}

自定义显示规则

以下代码展示了如何操作显示规则,例如隐藏所有标记、显示所有标记以及更改特定类型的多边形颜色。

void hideLocationsByDefault() async {
  final MPDisplayRule? main = await getMainDisplayRule();
  main?.setVisible(false);
}

void showLocationsByDefault() async {
  final MPDisplayRule? main = await getMainDisplayRule();
  main?.setVisible(true);
}

void changeTypePolygonColor(String type, Color color) async {
  final MPDisplayRule? rule = await getDisplayRuleByName(type);
  rule?.setPolygonFillColor(color);
}

完整示例

以下是一个完整的示例,展示了如何在 Flutter 应用中使用 mapsindoors_mapbox 插件。

import 'package:flutter/material.dart';
import 'package:mapsindoors_mapbox/mapsindoors.dart';
import 'package:mapsindoors_example/route_handler.dart';
import 'package:mapsindoors_example/example_position_provider.dart';

void main() {
  runApp(const MapsIndoorsDemoApp());
}

class MapsIndoorsDemoApp extends StatelessWidget {
  const MapsIndoorsDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MapsIndoors Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Map(apiKey: 'demo'),
    );
  }
}

class Map extends StatefulWidget {
  const Map({super.key, required this.apiKey});
  final String apiKey;

  @override
  State<Map> createState() => _MapState();
}

class _MapState extends State<Map> {
  final _scaffoldKey = GlobalKey<ScaffoldState>();
  PersistentBottomSheetController? _controller;

  late MapsIndoorsWidget _mapControl;
  final _positionProvider = ExamplePositionProvider();
  List<MPLocation> _searchResults = [];
  final _userPosition = MPPoint.withCoordinates(
      longitude: -77.03740973527613,
      latitude: 38.897389429704695,
      floorIndex: 0);
  RouteHandler? _routeHandler;

  @override
  void initState() {
    super.initState();
    loadMapsIndoors(widget.apiKey).then((error) {
      if (error == null) {
        setPositionProvider(_positionProvider);
      }
    });
  }

  void onMapControlReady(MPError? error) async {
    if (error == null) {
      _mapControl
        ..setOnLocationSelectedListener(onLocationSelected, false)
        ..goTo(await getDefaultVenue());

      _positionProvider.updatePosition(_userPosition, accuracy: 25, bearing: 0.0, floorIndex: 0);
    } else {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text("Map load failed: $error"),
        backgroundColor: Colors.red,
      ));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: SearchWidget(
          onSubmitted: search,
        ),
      ),
      drawer: Drawer(
        child: Flex(
          direction: Axis.vertical,
          children: [
            Expanded(
              child: _searchResults.isNotEmpty
                  ? ListView.builder(
                      itemBuilder: (ctx, i) {
                        return ListTile(
                          onTap: () {
                            _mapControl.selectLocation(_searchResults[i]);
                            _scaffoldKey.currentState?.closeDrawer();
                          },
                          title: Text(_searchResults[i].name),
                        );
                      },
                      itemCount: _searchResults.length,
                    )
                  : const Icon(Icons.search_off, color: Colors.black, size: 100.0),
            ),
          ],
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            _mapControl = MapsIndoorsWidget(
              readyListener: onMapControlReady,
            ),
          ],
        ),
      ),
    );
  }

  void search(String value) {
    if (value.isEmpty) {
      _mapControl.clearFilter();
      setState(() {
        _searchResults = [];
      });
      return;
    }

    MPQuery query = (MPQueryBuilder()..setQuery(value)).build();
    MPFilter filter = (MPFilterBuilder()..setTake(30)).build();

    final scaffold = ScaffoldMessenger.of(context);

    getLocationsByQuery(query: query, filter: filter).then((locations) {
      if (locations != null && locations.isNotEmpty) {
        setState(() {
          _searchResults = locations;
          _scaffoldKey.currentState?.openDrawer();
        });
        _mapControl.setFilterWithLocations(locations, MPFilterBehavior.DEFAULT);
      }
    }).catchError((err) {
      if (mounted) return;
      scaffold.showSnackBar(SnackBar(
        content: Text("Search failed: $err"),
        backgroundColor: Colors.red,
      ));
    });
  }

  void enableLiveData() {
    _mapControl
      ..enableLiveData(LiveDataDomainTypes.availability.name)
      ..enableLiveData(LiveDataDomainTypes.occupancy.name)
      ..enableLiveData(LiveDataDomainTypes.position.name);
  }

  void onLocationSelected(MPLocation? location) {
    if (location == null) {
      _controller?.close();
      _controller = null;
      return;
    }

    _routeHandler?.removeRoute();

    _controller = _scaffoldKey.currentState?.showBottomSheet((context) {
      return Container(
        color: Colors.white,
        padding: const EdgeInsets.only(bottom: 50.0, left: 100, right: 100),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const SizedBox(height: 30),
            Text(location.name),
            const SizedBox(height: 30),
            Text("Description: ${location.description}"),
            const SizedBox(height: 30),
            Text("Building: ${location.buildingName} - ${location.floorName}"),
            const SizedBox(height: 30),
            ElevatedButton(
              onPressed: () => _routeHandler = RouteHandler(
                  origin: _userPosition,
                  destination: location.point,
                  scaffold: _scaffoldKey.currentState!),
              child: const Row(
                children: [
                  Icon(Icons.keyboard_arrow_left_rounded),
                  SizedBox(width: 5),
                  Text("directions")
                ],
              ),
            ),
          ],
        ),
      );
    });
    _controller?.closed.then((value) => _mapControl.selectLocation(null));
  }
}

以上代码展示了如何在 Flutter 应用中使用 mapsindoors_mapbox 插件来实现室内地图显示、路径导航和地点搜索功能。希望这些示例对你有所帮助!


更多关于Flutter室内地图导航插件mapsindoors_mapbox的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter室内地图导航插件mapsindoors_mapbox的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用mapsindoors_mapbox插件来实现室内地图导航的示例代码。mapsindoors_mapbox是一个用于在Flutter应用中集成室内地图导航功能的插件,基于Mapbox技术。

首先,确保你的Flutter项目已经创建并配置好。然后,按照以下步骤集成mapsindoors_mapbox插件。

1. 添加依赖

在你的pubspec.yaml文件中添加mapsindoors_mapbox依赖:

dependencies:
  flutter:
    sdk: flutter
  mapsindoors_mapbox: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

2. 配置Mapbox和MapsIndoors

你需要在Mapbox和MapsIndoors网站上注册并获取API密钥和配置信息。假设你已经完成了这些步骤,并获得了以下信息:

  • Mapbox Access Token
  • MapsIndoors Venue ID
  • MapsIndoors Venue Key

3. 初始化插件

在你的Flutter应用的main.dart文件中,初始化mapsindoors_mapbox插件:

import 'package:flutter/material.dart';
import 'package:mapsindoors_mapbox/mapsindoors_mapbox.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter MapsIndoors Mapbox Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MapsIndoorsScreen(),
    );
  }
}

class MapsIndoorsScreen extends StatefulWidget {
  @override
  _MapsIndoorsScreenState createState() => _MapsIndoorsScreenState();
}

class _MapsIndoorsScreenState extends State<MapsIndoorsScreen> {
  @override
  void initState() {
    super.initState();
    // 初始化MapsIndoors
    MapsIndoorsMapbox.init(
      accessToken: '你的Mapbox Access Token',
      venueId: '你的MapsIndoors Venue ID',
      venueKey: '你的MapsIndoors Venue Key',
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Indoor Map Navigation'),
      ),
      body: MapsIndoorsMapboxWidget(
        // 可以添加其他配置参数
      ),
    );
  }
}

4. 运行应用

确保你已经正确配置了所有必要的API密钥和ID,然后运行你的Flutter应用:

flutter run

如果一切顺利,你应该能够在你的Flutter应用中看到一个室内地图。

5. 自定义和扩展

mapsindoors_mapbox插件提供了丰富的API,你可以根据需要进行自定义和扩展。例如,你可以添加室内定位、路径规划、标注等功能。

示例:添加标注

以下是如何在地图上添加标注的示例代码:

import 'package:mapsindoors_mapbox/mapsindoors_mapbox.dart' show MapFeature;

// ...

class _MapsIndoorsScreenState extends State<MapsIndoorsScreen> {
  // ...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Indoor Map Navigation'),
      ),
      body: MapsIndoorsMapboxWidget(
        onMapReady: () {
          // 添加标注
          MapsIndoorsMapbox.addMapFeature(
            MapFeature(
              id: 'my-feature-id',
              geometry: GeoJsonPoint(
                coordinates: [经度, 纬度],  // 替换为实际的经纬度
              ),
              properties: {
                'name': 'My Feature',
              },
            ),
          );
        },
      ),
    );
  }
}

在这个示例中,我们使用了onMapReady回调来确保地图已经加载完毕,然后添加了一个标注。

请注意,这只是一个基本的示例,实际项目中可能需要更多的配置和处理。建议查阅mapsindoors_mapbox的官方文档以获取更多详细信息。

回到顶部