@enduco/nativescript-mapbox
由 Mapbox 提供的本地地图
npm i --save @enduco/nativescript-mapbox

NativeScript Mapbox 插件

NPM version Downloads Twitter Follow

Mapbox 提供的强大本地 OpenGL 地图

在 Android 下,NativeScript Core Modules 的一个 bug 可能会导致导航时随机崩溃。请参阅 ./demo-angular/README.md 以获取解决方案。https://github.com/NativeScript/NativeScript/issues/7954 https://github.com/NativeScript/NativeScript/issues/7867

开始之前 - 前提条件

您需要自己的瓦片服务器,例如由 openmaptiles.org 提供的服务器,或者一个 Mapbox API 访问令牌(他们有一个 🆓 Starter 计划!),因此请在 Mapbox 上注册。注册后,转到您的帐户 > 应用 > 新令牌。'默认密钥令牌' 是您需要的。

您还需要设置您的开发环境。请参阅 NativeScript 文档

安装

$ tns plugin install nativescript-mapbox

示例

在存储库中提供了两个示例应用程序。

要运行它们,您需要克隆 GitHub 存储库并构建插件。请参阅以下内容。

您还需要一个访问令牌。然后您可以在顶级 mapbox_config.ts 文件中设置 access_token。

样式可以设置为 Mapbox 的任何样式名称,也可以是您自己的托管瓦片服务器的 URL。

注意:截至本文写作时,NativeScript 示例仅适用于 mapbox 令牌。demo-angular 将与自托管瓦片服务器或 mapbox 令牌一起工作。

运行 Angular 示例

cd src
npm run build.release
cd ../demo-angular
tns run <platform>

运行纯 Nativescript 示例

cd src
npm run build.release
cd ../demo
tns run <platform>

调试构建

为了更好地了解插件,我添加了大量的跟踪消息。您可以通过将上述命令中的 'npm run build.release' 替换为 'npm run build.debug' 来打开这些消息。

重大更改

此版本包含重大 API 更改。

未来的目标是尽可能多地映射 Mapbox GL JS API,以便在基于浏览器和原生应用程序之间共享映射代码。

问题

如果在 iOS 构建期间遇到与 Podspec 版本相关的错误,最简单的修复方法是:tns platform remove iostns platform add ios

在 Android 上,插件将此添加到 <application> 节点中 app/App_Resources/Android/AndroidManifest.xml(插件已经尝试这样做)

  <service android:name="com.mapbox.services.android.telemetry.service.TelemetryService" />

如果您遇到与 TelemetryService 相关的错误,请检查它是否存在。

使用方法

示例应用程序(XML + TypeScript)

如果您想快速入门,请参阅此存储库中的示例。它展示了如何使用 XML 和 JS 绘制具有几乎所有可能选项的地图。

示例应用程序(Angular)

此外,此存储库中的 demo-angular 还包含了 Angular 示例的初始版本。

在视图中声明地图

XML

您可以从 JS 或 TS 实例化地图。由于地图是另一个视图组件,它将与任何 NativeScript 布局很好地配合。您还可以轻松地将多个地图添加到同一页面或任何布局中的不同页面。

一个简单的布局可能看起来像这样

可以由以下定义渲染

<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:map="nativescript-mapbox" navigatingTo="navigatingTo">
<StackLayout>
<Label text="Nice map, huh!" class="title"/>
<ContentView height="240" width="240">
<map:MapboxView
accessToken="your_token"
mapStyle="traffic_night"
latitude="52.3702160"
longitude="4.8951680"
zoomLevel="3"
showUserLocation="true"
mapReady="onMapReady">

</map:MapboxView>
</ContentView>
</StackLayout>
</Page>

Angular

组件

import { registerElement } from "nativescript-angular/element-registry";
registerElement("Mapbox", () => require("nativescript-mapbox").MapboxView);

视图

  <ContentView height="100%" width="100%">
<Mapbox
accessToken="your_token"
mapStyle="traffic_day"
latitude="50.467735"
longitude="13.427718"
hideCompass="true"
zoomLevel="18"
showUserLocation="false"
disableZoom="false"
disableRotation="false"
disableScroll="false"
disableTilt="false"
(mapReady)="onMapReady($event)">

</Mapbox>
</ContentView>

可用的XML/Angular选项

您基于XML的地图当前所有支持选项是(不要使用其他属性 - 如果您需要样式,请将地图包裹在ContentView中,并将如width之类的属性应用于该容器!)

选项 默认值 描述
accesstoken - 见上方的'先决条件'部分
delay 0 以毫秒为单位的延迟 - 您可以设置此值以更好地控制Mapbox的调用时机,以免与您应用程序可能需要执行的其他计算冲突。
mapStyle streets streets, light, dark, satellite_streets, satellite, traffic_day, traffic_night, 以mapbox://或指向自定义JSON定义的URL开头(http://, https://,或相对于nativescript应用程序路径的本地相对路径 ~/)
latitude - 通过传递此参数设置地图的中心
longitude - 以及这个
zoomLevel 0 0-20
showUserLocation false 在Android上需要位置权限,如果您不需要,可以从AndroidManifest.xml中删除
hideCompass false 在地图旋转时不要显示右上角的指南针
hideLogo false 如果您处于免费计划,Mapbox需要false
hideAttribution true 如果您处于免费计划,Mapbox需要false
disableZoom false 不允许用户缩放(捏合和双击)
disableRotation false 不允许用户旋转地图(双指手势)
disableScroll false 不允许用户移动地图的中心(单指拖动)
disableTilt false 不允许用户倾斜地图(双指向上或向下拖动)
mapReady - 您可以声明的回调函数的名称,用于在地图绘制后与之交互
moveBeginEvent - 当地图移动时将被调用的函数的名称
locationPermissionGranted - 您可以声明的回调函数的名称,用于在用户授予位置权限时得到通知
locationPermissionDenied - 您可以声明的回调函数的名称,用于在用户拒绝位置权限时得到通知(iOS上永远不会触发,因为没有可以拒绝的内容)

想添加标记吗?

这就是上表中最后一个选项的作用 - mapReady。它允许您在地图绘制到页面上之后与之交互。

打开 main-page.[js|ts] 并添加此(有关完整的标记API,请参见下方的addMarkers

var mapbox = require("nativescript-mapbox");

function onMapReady(args) {
// you can tap into the native MapView objects (MGLMapView for iOS and com.mapbox.mapboxsdk.maps.MapView for Android)
var nativeMapView = args.ios ? args.ios : args.android;
console.log("Mapbox onMapReady for " + (args.ios ? "iOS" : "Android") + ", native object received: " + nativeMapView);

// .. or use the convenience methods exposed on args.map, for instance:
args.map.addMarkers([
{
lat: 52.3602160,
lng: 4.8891680,
title: 'One-line title here',
subtitle: 'Really really nice location',
selected: true, // makes the callout show immediately when the marker is added (note: only 1 marker can be selected at a time)
onCalloutTap: function(){console.log("'Nice location' marker callout tapped");}
}]
);
}

exports.onMapReady = onMapReady;

或者想要设置视口边界?

var mapbox = require("nativescript-mapbox");

function onMapReady(args) {
args.map.setViewport(
{
bounds: {
north: 52.4820,
east: 5.1087,
south: 52.2581,
west: 4.6816
},
animated: true
}
);
}

exports.onMapReady = onMapReady;

您可以从XML声明的地图调用如下方法:addMarkerssetViewportremoveMarkersgetCentersetCentergetZoomLevelsetZoomLevelgetViewportgetTiltsetTiltsetMapStyleanimateCameraaddPolygonremovePolygonsaddPolylineremovePolylinesgetUserLocationtrackUsersetOnMapClickListenersetOnMapLongClickListenerdestroy

请查看以下函数的使用详情。

程序化声明地图

将容器添加到您想要程序化添加地图的视图XML中。给它一个ID。

<ContentView id="mapContainer" />

显示


const contentView : ContentView = <ContentView>page.getViewById( 'mapContainer' );

const settings = {

// NOTE: passing in the container here.

container: contentView,
accessToken: ACCESS_TOKEN,
style: MapStyle.LIGHT,
margins: {
left: 18,
right: 18,
top: isIOS ? 390 : 454,
bottom: isIOS ? 50 : 8
},
center: {
lat: 52.3702160,
lng: 4.8951680
},
zoomLevel: 9, // 0 (most of the world) to 20, default 0
showUserLocation: true, // default false
hideAttribution: true, // default false
hideLogo: true, // default false
hideCompass: false, // default false
disableRotation: false, // default false
disableScroll: false, // default false
disableZoom: false, // default false
disableTilt: false, // default false
markers: [
{
id: 1,
lat: 52.3732160,
lng: 4.8941680,
title: 'Nice location',
subtitle: 'Really really nice location',
iconPath: 'res/markers/green_pin_marker.png',
onTap: () => console.log("'Nice location' marker tapped"),
onCalloutTap: () => console.log("'Nice location' marker callout tapped")
}
]
};

console.log( "main-view-model:: doShow(): creating new MapboxView." );

const mapView = new MapboxView();

// Bind some event handlers onto our newly created map view.

mapView.on( 'mapReady', ( args : any ) => {

console.log( "main-view-model: onMapReady fired." );

// this is an instance of class MapboxView

this.mapboxView = args.map;

// get a reference to the Mapbox API shim object so we can directly call its methods.

this.mapbox = this.mapboxView.getMapboxApi();

this.mapbox.setOnMapClickListener( point => {
console.log(`>> Map clicked: ${JSON.stringify(point)}`);
return true;
});

this.mapbox.setOnMapLongClickListener( point => {
console.log(`>> Map longpressed: ${JSON.stringify(point)}`);
return true;
});

this.mapbox.setOnScrollListener((point: LatLng) => {
// console.log(`>> Map scrolled`);
});

this.mapbox.setOnFlingListener(() => {
console.log(`>> Map flinged"`);
}).catch( err => console.log(err) );

});

mapView.setConfig( settings );
contentView.content = mapView;

隐藏

所有后续示例都假定已经通过mapbox进行了要求。此外,所有函数都支持promises,但我们为了简洁起见省略了.then(),因为它并不增加价值。

  mapbox.hide();

取消隐藏

如果您之前调用了hide(),可以快速取消隐藏地图,而不是重新绘制它(这要慢得多,并且您会丢失视口位置等)。

  mapbox.unhide();

销毁 💥

为了完全清理地图,您可以使用销毁而不是隐藏它

  mapbox.destroy();

setMapStyle

您可以在加载地图后更新地图样式。

在使用Mapbox Android SDK 6.1.x(用于插件版本4.1.0)后,我观察到Android设备会在几秒钟后崩溃,所以请在使用时仔细测试,并在不确定的情况下避免使用。

  mapbox.setMapStyle(mapbox.MapStyle.DARK);

addMarkers

  import { MapboxMarker } from "nativescript-mapbox";

const firstMarker = <MapboxMarker>{ //cast as a MapboxMarker to pick up helper functions such as update()
id: 2, // can be user in 'removeMarkers()'
lat: 52.3602160, // mandatory
lng: 4.8891680, // mandatory
title: 'One-line title here', // no popup unless set
subtitle: 'Infamous subtitle!',
// icon: 'res://cool_marker', // preferred way, otherwise use:
icon: 'http(s)://website/coolimage.png', // from the internet (see the note at the bottom of this readme), or:
iconPath: 'res/markers/home_marker.png',
selected: true, // makes the callout show immediately when the marker is added (note: only 1 marker can be selected at a time)
onTap: marker => console.log("Marker tapped with title: '" + marker.title + "'"),
onCalloutTap: marker => alert("Marker callout tapped with title: '" + marker.title + "'")
};

mapbox.addMarkers([
firstMarker,
{
// more markers..
}
])

更新标记

插件版本4.2.0增加了更新标记的选项。只需在上文创建的MapboxMarker引用上调用update即可。您可以更新以下属性(除了图标之外的所有属性)

  firstMarker.update({
lat: 52.3622160,
lng: 4.8911680,
title: 'One-line title here (UPDATE)',
subtitle: 'Updated subtitle',
selected: true, // this will trigger the callout upon update
onTap: (marker: MapboxMarker) => console.log(`UPDATED Marker tapped with title: ${marker.title}`),
onCalloutTap: (marker: MapboxMarker) => alert(`UPDATED Marker callout tapped with title: ${marker.title}`)
})

removeMarkers

您可以通过不传入参数来删除所有标记,或者删除特定的标记ID(您之前指定的)。

  // remove all markers
mapbox.removeMarkers();

// remove specific markers by id
mapbox.removeMarkers([1, 2]);

setViewport

如果您想将视口包含所有标记,可以使用此函数将边界设置为最外围标记的经纬度。

  mapbox.setViewport(
{
bounds: {
north: 52.4820,
east: 5.1087,
south: 52.2581,
west: 4.6816
},
animated: true // default true
}
)

getViewport

  mapbox.getViewport().then(
function(result) {
console.log("Mapbox getViewport done, result: " + JSON.stringify(result));
}
)

setCenter

  mapbox.setCenter(
{
lat: 52.3602160, // mandatory
lng: 4.8891680, // mandatory
animated: false // default true
}
)

getCenter

在这里,使用承诺回调是有意义的,所以将其添加到示例中

  mapbox.getCenter().then(
function(result) {
console.log("Mapbox getCenter done, result: " + JSON.stringify(result));
},
function(error) {
console.log("mapbox getCenter error: " + error);
}
)

setZoomLevel

  mapbox.setZoomLevel(
{
level: 6.5, // mandatory, 0-20
animated: true // default true
}
)

getZoomLevel

  mapbox.getZoomLevel().then(
function(result) {
console.log("Mapbox getZoomLevel done, result: " + JSON.stringify(result));
},
function(error) {
console.log("mapbox getZoomLevel error: " + error);
}
)

animateCamera

  // this is a boring triangle drawn near Amsterdam Central Station
mapbox.animateCamera({
// this is where we animate to
target: {
lat: 52.3732160,
lng: 4.8941680
},
zoomLevel: 17, // Android
altitude: 2000, // iOS (meters from the ground)
bearing: 270, // Where the camera is pointing, 0-360 (degrees)
tilt: 50,
duration: 5000 // default 10000 (milliseconds)
})

setTilt(仅限Android)

  mapbox.setTilt(
{
tilt: 40, // default 30 (degrees angle)
duration: 4000 // default 5000 (milliseconds)
}
)

getTilt(仅限Android)

  mapbox.getTilt().then(
function(tilt) {
console.log("Current map tilt: " + tilt);
}
)

getUserLocation

如果用户的地理位置在地图上显示,您可以获取他们的坐标和速度。

  mapbox.getUserLocation().then(
function(userLocation) {
console.log("Current user location: " + userLocation.location.lat + ", " + userLocation.location.lng);
console.log("Current user speed: " + userLocation.speed);
}
)

trackUser

如果您正在显示用户的地理位置,您可以让地图跟踪位置。地图将随着最后已知位置连续移动。

  mapbox.trackUser({
mode: "FOLLOW_WITH_HEADING", // "NONE" | "FOLLOW" | "FOLLOW_WITH_HEADING" | "FOLLOW_WITH_COURSE"
animated: true
});

addSource

https://docs.mapbox.com/mapbox-gl-js/api/#map#addsource

向地图添加GeoJSON源向量的。

  mapbox.addSource( id, {
type: 'vector',
url: 'url to source'
} );

-或-

  mapbox.addSource( id, {
'type': 'geojson',
'data': {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [ [ lng, lat ], [ lng, lat ], ..... ]
}
}
}
);

removeSource

通过ID移除源。

  mapbox.removeSource( id );

addLayer

注意:对于版本5,addLayer()的API已更改,现在是web-gl-js API的子集。

https://docs.mapbox.com/mapbox-gl-js/style-spec/#layers

添加线

  mapbox.addLayer({
'id': someid,
'type': 'line',
'source': {
'type': 'geojson',
'data': {
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [ [ lng, lat ], [ lng, lat ], ..... ]
}
}
}
},
'layout': {
'line-cap': 'round',
'line-join': 'round'
},
'paint': {
'line-color': '#ed6498',
'line-width': 5,
'line-opacity': .8,
'line-dash-array': [ 1, 1, 1, ..]
}
});

添加圆圈

  mapbox.addLayer({
"id": someid,
"type": 'circle',
"radius-meters": 500, // FIXME: radius in meters used for in-circle click detection.
"source": {
"type": 'geojson',
"data": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [ lng, lat ]
}
}
},
"paint": {
"circle-radius": {
"stops": [
[0, 0],
[20, 8000 ]
],
"base": 2
},
'circle-opacity': 0.05,
'circle-color': '#ed6498',
'circle-stroke-width': 2,
'circle-stroke-color': '#ed6498'
}
});

源可以是geojson或向量源描述,也可以是使用addSource()添加的源ID。

removeLayer

通过ID移除使用addLayer()添加的图层。

  mapbox.removeLayer( id );

addLinePoint

动态向线添加点。

  mapbox.addLinePoint( <id of line layer>, lnglat )

其中lnglat是两个点的数组,一个是经度,一个是纬度。

addPolygon

绘制一个形状。就像我们小时候那样连接点。

第一个用此函数画雪人的推文者将获得一件T恤。

  // after adding this, scroll to Amsterdam to see a semi-transparent red square
mapbox.addPolygon(
{
id: 1, // optional, can be used in 'removePolygons'
fillColor: new Color("red"),
fillOpacity: 0.7,

// stroke-related properties are only effective on iOS
strokeColor: new Color("green"),
strokeWidth: 8,
strokeOpacity: 0.5,

points: [
{
lat: 52.3923633970718,
lng: 4.902648925781249
},
{
lat: 52.35421556258807,
lng: 4.9308013916015625
},
{
lat: 52.353796172573944,
lng: 4.8799896240234375
},
{
lat: 52.3864966440161,
lng: 4.8621368408203125
},
{
lat: 52.3923633970718,
lng: 4.902648925781249
}
]
})
.then(result => console.log("Mapbox addPolygon done"))
.catch((error: string) => console.log("mapbox addPolygon error: " + error));

removePolygons

您可以通过不传入参数来删除所有多边形,或者删除特定的多边形ID(您之前指定的)。

  // remove all polygons
mapbox.removePolygons();

// remove specific polygons by id
mapbox.removePolygons([1, 2]);

addPolyline

已弃用。请使用addLayer()代替。

绘制多段线。连接作为参数给出的点。

  // Draw a two segment line near Amsterdam Central Station
mapbox.addPolyline({
id: 1, // optional, can be used in 'removePolylines'
color: '#336699', // Set the color of the line (default black)
width: 7, // Set the width of the line (default 5)
opacity: 0.6, //Transparency / alpha, ranging 0-1. Default fully opaque (1).
points: [
{
'lat': 52.3833160, // mandatory
'lng': 4.8991780 // mandatory
},
{
'lat': 52.3834160,
'lng': 4.8991880
},
{
'lat': 52.3835160,
'lng': 4.8991980
}
]
});

removePolylines

已弃用。请使用removeLayer()代替。

您可以通过不传入参数来删除所有多段线,或者删除特定的多段线ID(您之前指定的)。

  // remove all polylines
mapbox.removePolylines();

// remove specific polylines by id
mapbox.removePolylines([1, 2]);

addSource

添加一个可以被addLayer使用的源。注意目前只支持vector类型。

  mapbox.addSource(
id: "terrain-source", // required
type: "vector", // supported types: vector
url: "mapbox://mapbox.mapbox-terrain-v2"
);

removeSource

通过id移除源。

  mapbox.removeSource("terrain-source");

addLayer

从源添加图层到地图。注意目前只支持circlefillline类型。

  mapbox.addLayer(
id: "terrain-data", // required
source: "terrain-source", // id of source
sourceLayer: "contour", // id of layer from source
type: "line", // supported types: circle, fill, line
lineJoin: "round",
lineCap: "round",
lineColor: "#ff69b4",
lineWidth: 1,
);

removeLayer

通过id移除图层。

  mapbox.removeLayer("terrain-data");

setOnMapClickListener

添加一个监听器以检索用户在地图上点击(不是标记)处的经纬度。

  mapbox.setOnMapClickListener((point: LatLng) => {
console.log("Map clicked at latitude: " + point.lat + ", longitude: " + point.lng);
});

setOnMapLongClickListener

添加一个监听器以检索用户在地图上长按(不是标记)处的经纬度。

  mapbox.setOnMapLongClickListener((point: LatLng) => {
console.log("Map longpressed at latitude: " + point.lat + ", longitude: " + point.lng);
});

setOnScrollListener

添加一个监听器以检索用户在地图上滚动到的经纬度。

  mapbox.setOnScrollListener((point?: LatLng) => {
console.log("Map scrolled to latitude: " + point.lat + ", longitude: " + point.lng);
});

离线地图

对于您想要用户预先加载特定区域的场景,您可以使用这些方法创建和删除离线区域。

重要阅读: Mapbox的离线地图文档

downloadOfflineRegion

此示例在地图样式“outdoors”下将区域“Amsterdam”的9、10和11级下载到。

  mapbox.downloadOfflineRegion(
{
accessToken: accessToken, // required for Android in case no map has been shown yet
name: "Amsterdam", // this name can be used to delete the region later
style: mapbox.MapStyle.OUTDOORS,
minZoom: 9,
maxZoom: 11,
bounds: {
north: 52.4820,
east: 5.1087,
south: 52.2581,
west: 4.6816
},
// this function is called many times during a download, so
// use it to show an awesome progress bar!
onProgress: function (progress) {
console.log("Download progress: " + JSON.stringify(progress));
}
}
).then(
function() {
console.log("Offline region downloaded");
},
function(error) {
console.log("Download error: " + error);
}
);

高级示例:下载当前视口

使用mapbox.getViewport()函数获取视口,并在不同的缩放级别下载它

  // I spare you the error handling on this one..
mapbox.getViewport().then(function(viewport) {
mapbox.downloadOfflineRegion(
{
name: "LastViewport", // anything you like really
style: mapbox.MapStyle.LIGHT,
minZoom: viewport.zoomLevel,
maxZoom: viewport.zoomLevel + 2, // higher zoom level is lower to the ground
bounds: viewport.bounds,
onProgress: function (progress) {
console.log("Download %: " + progress.percentage);
}
}
);
});

listOfflineRegions

为了帮助您管理离线区域,您可以使用listOfflineRegions函数。然后,您可以调用deleteOfflineRegion(见下文),传入name来删除您喜欢的任何已缓存的区域。

  mapbox.listOfflineRegions({
// required for Android in case no map has been shown yet
accessToken: accessToken
}).then(
function(regions) {
console.log(JSON.stringify(JSON.stringify(regions));
},
function(error) {
console.log("Error while listing offline regions: " + error);
}
);

deleteOfflineRegion

您可以删除之前下载的区域。任何与name参数匹配的区域将被本地删除。

  mapbox.deleteOfflineRegion({
name: "Amsterdam"
}).then(
function() {
console.log("Offline region deleted");
},
function(error) {
console.log("Error while deleting an offline region: " + error);
}
);

权限

hasFineLocationPermission / requestFineLocationPermission

在Android 6上,您需要请求权限才能在目标API级别23+时在运行时显示用户在地图上的位置。即使uses-permission标签中存在ACCESS_FINE_LOCATION,也需要在AndroidManifest.xml中。

对于2.4.0+版本的插件,您不需要这样做,因为请求权限是在渲染地图时进行的。欢迎:)

请注意,当以下情况发生时,hasFineLocationPermission将返回true:

  • 您在iOS上运行此操作,或者
  • 您针对的API级别低于23,或者
  • 您使用Android < 6,或者
  • 您已经授权了权限。
  mapbox.hasFineLocationPermission().then(
function(granted) {
// if this is 'false' you probably want to call 'requestFineLocationPermission' now
console.log("Has Location Permission? " + granted);
}
);

// if no permission was granted previously this will open a user consent screen
mapbox.requestFineLocationPermission().then(
function() {
console.log("Location permission requested");
}
);

请注意,如果传入showUserLocation : true,则show函数也会检查权限。如果您在显示地图之前没有请求权限,并且需要权限,则插件将在渲染地图时请求用户权限。

使用来自互联网的标记图像

如果您指定icon: 'http(s)://some-remote-image',则在iOS上您需要将域名列入白名单。在Google中搜索iOS ATS以获取详细选项,但为了快速测试,您可以将以下内容添加到app/App_Resources/iOS/Info.plist

	<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>