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

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

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

简介

mapsindoors_googlemaps 是一个Flutter插件,用于集成原生的MapsIndoors SDK,支持在Android和iOS平台上展示室内地图和导航。

支持平台

平台 Android iOS
支持 SDK 21+ 14.0+

功能

  • 展示室内地图和导航。
  • 执行实时路径规划。
  • 查看位置的实时更新。

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

快速开始

添加依赖

pubspec.yaml 文件中添加 mapsindoors_googlemaps

dependencies:
  mapsindoors_googlemaps: ^4.1.0

Android设置

Google Maps设置

为了使底层的Google Map正常工作,请执行以下步骤:

  1. 导航到 android/app/src/main/res/values
  2. 在此文件夹中创建一个名为 google_maps_api_key.xml 的文件。
  3. 复制并粘贴以下代码片段,并将 YOUR_KEY_HERE 替换为您的Google Maps API密钥。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="google_maps_key">YOUR_KEY_HERE</string>
</resources>

Gradle设置

确保插件能够解析其MapsIndoors Gradle依赖项,请执行以下操作:

  1. 导航到应用程序项目的项目级 build.gradle
  2. maven { url 'https://maven.mapsindoors.com/' } 添加到 allprojects/repositories 中,位于 mavenCentral() 之后。
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://maven.mapsindoors.com/' }
    }
}

iOS设置

Google Maps设置

为了使底层的Google Map正常工作,请执行以下步骤:

  1. 获取Google Maps的API密钥:获取API密钥
  2. 导航到 iOS/Runner/Info.plist
  3. 添加一个新的键 GoogleMapsAPIKey,内容为您的Google Maps API密钥。

使用方法

以下部分提供了代码示例,展示了如何完成以下任务:

显示地图

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

可选地,我们可以添加一个 MPFloorSelector 到地图中。这里我们使用 MPDefaultFloorSelector,因为它包含在MapsIndoors包中。选择器必须同时添加到构建树以及 MapControl 中才能正常工作。

一旦调用 initState()MapsIndoors 开始初始化,成功完成后,MapControl 开始初始化。

初始化完成后,可以调用 goTo 方法将相机移动到默认场地。

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, 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: $err");
        }
    }
}

显示路线

以下代码片段初始化了 MPDirectionsServiceMPDirectionsRenderer 类,并使用已经初始化的 _mapControl

showRouteToLocation 函数用于查询从用户当前位置到指定位置的路线。

如果成功,则使用 directionsRenderer.setRoute(route) 在地图上显示路线。

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: _userPosition, destination: location.point).then((route) {
        directionsRenderer.setRoute(route);
    }).catchError((err) => print("An error occured: $err"));
}

搜索位置

以下代码片段展示了如何使用 MapsIndoors 搜索匹配查询字符串 "parking" 的位置。

它会在位置的描述、名称和外部ID中进行匹配。搜索完成后,可以从这些位置获取信息(未在代码片段中指定)。

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 near the point: ${locations?.length}");
        // do something with the locations
    });
}

更改外观

以下代码片段展示了三种方式来操作显示规则:

  • hideLocationsByDefault() 方法隐藏所有未明确可见的标记。
  • showLocationsByDefault() 方法确保所有标记都显示。
  • changeTypePolygonColor(String type, Color color) 方法更改特定类型的多边形填充颜色。
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);
}

示例Demo

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

import 'package:flutter/material.dart';
import 'package:mapsindoors_googlemaps/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: <Widget>[
            _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));
  }
}

以上是关于 mapsindoors_googlemaps 插件的基本使用方法和完整示例代码。希望对您有所帮助!


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

1 回复

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


在Flutter项目中集成和使用mapsindoors_googlemaps插件来实现室内地图导航功能,可以通过以下步骤和代码示例来完成。mapsindoors_googlemaps插件结合了Google Maps和MapsIndoors的功能,为开发者提供强大的室内地图导航能力。

步骤 1: 添加依赖

首先,你需要在pubspec.yaml文件中添加mapsindoors_googlemaps依赖:

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

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

步骤 2: 配置Google Maps API密钥

为了使用Google Maps,你需要在Google Cloud Platform上创建一个项目并启用Google Maps JavaScript API和Google Maps Places API,然后获取API密钥。

android/app/src/main/AndroidManifest.xml中添加API密钥:

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="你的API密钥"/>

对于iOS,你需要在Info.plist中添加API密钥:

<key>GOOGLE_MAPS_API_KEY</key>
<string>你的API密钥</string>

步骤 3: 初始化MapsIndoors和Google Maps

在你的Flutter应用中,你需要初始化MapsIndoorsGoogleMapsController,并设置室内地图的相关配置。以下是一个基本的代码示例:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: IndoorMapScreen(),
    );
  }
}

class IndoorMapScreen extends StatefulWidget {
  @override
  _IndoorMapScreenState createState() => _IndoorMapScreenState();
}

class _IndoorMapScreenState extends State<IndoorMapScreen> {
  MapsIndoorsGoogleMapsController? _controller;

  @override
  void initState() {
    super.initState();
    // 初始化MapsIndoorsGoogleMapsController
    _controller = MapsIndoorsGoogleMapsController(
      venueId: '你的场馆ID', // 替换为你的场馆ID
      apiKey: '你的API密钥', // 替换为你的Google Maps API密钥
      onVenueLoaded: () {
        print('场馆加载完成');
      },
      onError: (error) {
        print('加载场馆时出错: $error');
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('室内地图导航'),
      ),
      body: MapsIndoorsGoogleMaps(
        controller: _controller!,
        options: MapsIndoorsGoogleMapsOptions(
          // 设置室内地图的选项,如初始位置、缩放级别等
          cameraPosition: CameraPosition(
            target: LatLng(纬度, 经度), // 替换为场馆的初始坐标
            zoom: 18.0,
          ),
          // 其他选项...
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }
}

步骤 4: 处理室内导航和交互

mapsindoors_googlemaps插件提供了丰富的API来处理室内导航和用户交互。例如,你可以监听地图上的点击事件,或者根据用户的位置更新UI。

class _IndoorMapScreenState extends State<IndoorMapScreen> {
  // ...其他代码...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // ...其他代码...
      body: MapsIndoorsGoogleMaps(
        controller: _controller!,
        options: MapsIndoorsGoogleMapsOptions(
          // ...其他选项...
        ),
        onMapCreated: (controller) {
          // 地图创建完成后可以添加更多自定义逻辑
        },
        onMarkerTap: (marker) {
          // 处理标记点击事件
          print('点击了标记: ${marker.title}');
        },
        // 其他事件监听器...
      ),
    );
  }

  // ...其他代码...
}

以上代码展示了如何在Flutter项目中集成和使用mapsindoors_googlemaps插件来实现室内地图导航功能。根据具体需求,你可以进一步自定义和扩展这些功能。

回到顶部