kikita & Maps

GIS,spatial and artificial intellegence learning and share

通过上一个入门篇,相信你已经对 HERE mSDK 有了一定的印象。
作为一个地图SDK,最基础最重要的功能首先是地图渲染, 那么今天我们仍然以 HERE mSDK (Premium Edition)为例一起了解下。在 HERE Location Services 中有一个 Interactive Maps Service,用于访问服务器端预渲染的瓦片服务/tile map, 这就是今天要用到的原料。

Let’s get our hands dirty !
今天这篇我们先从最基础的 Map Rendering 和 Map Schemes 开始。 AndroidXMapFragment 类提供了交互式地图体验支持, 例如缩放、平移等等。AndroidXMapFragment初始化之后,可以通过Map map = mapFragment.getMap(); 获取 Map
Map 类中提供了很多 map attributes 帮助开发者确定地图的展示方式,例如:map center,zoom level, tilt, orientation, map projection, map animation ……

详见开发文档:Map

HERE Interactive Maps 提供了很多样式的地图外观(Map Scheme)可供选择,以满足不同审美和专题的需求。简而言之,地图数据是一套,皮肤可有很多种。

在开发者文档中,有一页图文可供参考:Map Scheme

HERE 在 Github上有官方的示例代码库,这篇的相关示例可以在这个连接中找到:Map Attribute

我在这个工程中稍作修改,增加了 Satallite Mode 和 Night Mode, 因为后者是最近备受欢迎的一种渲染方式,这么好看的地图不做在样例里觉得有点可惜。 Night Mode 一方面可以作为夜晚时段的地图展示,另一方面可以作为设备暗色主题搭配的地图显示。
另外,这个示例工程中,还包含了公交/Transit 主题地图的展示(None/公交站/公交线路),以及路况/Realtime Traffic Service 的展示 (Traffic Flow / Incident)。

Happy testing ^^

如下是在 Nexus 10 模拟器中的录屏:

今天整理下 HERE SDK的入门攻略~

HERE mobile SDK 是HERE location Servie(HLS)产品家族中重要的一部分,支持 Android 和 iOS 两大主流系统;从功能上分为 Lite(旧版本称 Starter)和 Premium 两个license level,顾名思义,前者支持较轻量级的功能,后者具有更专业更高级的功能。具体的功能清单和版本对比,可以在点击 官网链接 查到。

HERE Developer 网站提供免费测试账号,建议大家注册一个玩一玩。每个 Freemium Plan 账号下的 Project 可以拥有 Rest API、JS API、mSDK、studio 等很多产品的测试权限。对于个人 developer 来说,我认为足矣。

Freemium Plan 福利内容

Location Services

  • 250K Transactions per month
  • 5K SDK Monthly Active Users
  • 250 Assets per month
  • Pay per additional Transactions

Studio

  • 2.5GB Data transfer per month
  • 5GB Database storage per month
  • Pay per additional Data transfer or Database storage

Steps

获取Credentials及SDK安装包

登录 HERE Developer 账户之后,即可在自己创建的Project中生成每个产品对应的密钥/credentials。

在 SDK 密钥下方就有 SDK安装包可供下载。如下图即为 HERE mSDK (premium edition) 的示例:

检查 System Requirments

这是安装一切软件产品之前,仔细阅读官方系统要求,永远是不错的选择。

快速运行 Tutorial Example

下载后的SDK 安装包中包含了 user guild,release notes,SDK libraries, tutorial example 这几大件。 如果要快速入门,tutorial example 运行起来是必须的啦。

这里假设大家使用的开发环境都是 Android Studio, 解压缩 SDK 安装包后,开始吧!

  1. 启动 Android Studio,选择 open an existing Android Studio project, 浏览至 tutorial example 所在目录, .../HERE-sdk/tutorial/BasicMapSolution

  2. 接着会发现一条报错 Error: Failed to resolve: :HERE-sdk:, 这个不用担心,是因为 HERE SDK 的 libraries 默认没有包含在这个工程中。

  3. BasicMapSolution 中的 app 文件夹下创建一个名为 libs 的文件夹,并将安装包中的 HERE-sdk/libs 下的 HERE-sdk.aar 复制到这里。上步遇到的问题就解决料。

  4. 检查 app/build.gradle 文件,确保如下内容已经包含:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    repositories {
    flatDir {
    dirs 'libs'
    }
    }
    dependencies {
    implementation(name:'HERE-sdk', ext:'aar')
    // Depending on your specific project configuration you may have other entries here.
    }
  5. 准备许可。每个 application 都需要一套单独的credentials。需要注意:为app申请credentials时,填写的 package name 需要和注册的bundle identifier 完全一致。就这个 sample来说, 应该使用 package name: com.here.android.tutorial 。如果在文章的开始,你已经注册了credential,那也没有关系,在 developer 网站的 project 页面中,你还可以修改哦,同时密钥会重新刷一遍,请使用最新的。

  6. 写入许可。 打开目录 BasicMapSolution/app/src/main/ 下的AndroidManifest.xml文件。找到三行 <meta-data>开头的语句,填写 credentials 即可。

    1
    2
    3
    <meta-data android:name="com.here.android.maps.appid" android:value="{YOUR_APP_ID}"/>
    <meta-data android:name="com.here.android.maps.apptoken" android:value="{YOUR_APP_CODE}"/>
    <meta-data android:name="com.here.android.maps.license.key" android:value="{YOUR_LICENSE_KEY}"/>
  7. 连接设备,运行程序,Bingo !

Troubleshooting

测试过程中,不确定大家有没有遇到我曾遇到的问题?

This project uses AndroidX dependencies, but the 'android.useAndroidX' property is not enabled. Set this property to true in the gradle.properties file and retry.

解决方法:

在sample project 中的 gradle.properties文件中,增加一句:

1
android.useAndroidX=true

效果图

最后上图,我手头没有 Android 设备,用了 Nexus 10 模拟器:

又是很久没来更新,以至于kikitamap.com这个域名因为没有续费而已经停用接近两周;要不是今晚忽然想写点什么,还不知道什么时候我才能发现。

工作结束了,宝贝睡下了,每到接近12点,似乎属于我自己的完整时间才开始展开。所以,我的母亲总是不解,为什么终于到了你的休息时间,反而又要”熬夜“ ?事情不能明天做吗? 我总是无言以对,我这种矫情的时间独占需求,估计她不会理解,只好安抚她说马上就睡。大概,也不是所有人都有与自己独处的强需求。

我喜欢拍照,拍各种自认为有趣的东西,因此手机空间经常告急,只好翻一翻,删除一些不喜欢的或是没有什么内容的。忽然发现了这张很久前拍的。

当时的场景是我在等人,刚好从站的位置,看到自己的影子叠在橱窗里的模特身上,感觉很有点意思,就顺手拍了下来。世间熙熙攘攘,我们大多数都是追寻成为模特壳子的样子;住在壳子里面的人的样子呢?ta们是同一个吗?

在设备开启WiFi之后,会对周边 WiFi Access Point (AP)进行扫描,并将这些可见WiFi名称推给用户供选择。这是几乎是我们的PC和手机的日常。 除了连接WiFi登录Internet 之外,实际上,我们还可以通过 WiFi AP进行定位。

定位和地图一直是紧密联系的,无论是面向消费者的第一人称应用,还是关于管理者的追踪需求。各大LBS厂商,早就提供了各种定位功能,尤其HERE,Positioning 历史可以追溯到 Nokia年代,包括基于 GPS,WiFi network,Cell Signal,Bluetooth 等各种信号源的定位能力。


Concept

  • 除了Mobile Hotspot、WiFi Egg,大多数情况下 AP不会经常的移动, 这就为WiFi定位的基础参照位置信息;

  • 理论上,每一个 AP 都有全球唯一标识符— mac地址;当然也不排除有重复mac的问题,至于如何去重和更新后端数据库就是 another story,这里不展开了;

  • 设备WiFi扫描到AP即可同时获取AP的信息,信号的强度,mac地址(BSSID)等,无论是否连接上;

  • 根据WiFi信号强度衰减和距离的函数,可以判断设备离AP的距离;如果多个AP存在,那么这多个圆相交的区域就可以判定设备的位置;

从上面的原理可以看出,AP的位置越准确,设备扫描到的AP越多,那么设备的位置越准确。


Quick Testing

做个小测试,帮助快速理解。

假设已经拥有了 HERE Developer 账号和密钥,如果没有,这里 免费注册

以我手头的 Windows 和 HERE Positioning API 的 WLAN Positioning为例。


1 获取周边WiFi信息,可以使用 netsh 命令查到当前扫描到的AP。

netsh wlan show networks mode=BSSID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
C:\WINDOWS\system32>netsh wlan show networks mode=BSSID

Interface name : Wi-Fi
There are 18 networks currently visible.

SSID 1 : guest
Network type : Infrastructure
Authentication : WPA2-Personal
Encryption : CCMP
BSSID 1 : 0a:69:6c:0c:08:c2
Signal : 30%
Radio type : 802.11n
Channel : 11
Basic rates (Mbps) : 1 2 5.5 11
Other rates (Mbps) : 6 9 12 18 24 36 48 54
...
...
...

SSID 18 : Regus-76
Network type : Infrastructure
Authentication : WPA2-Personal
Encryption : CCMP
BSSID 1 : 0c:d8:6c:9f:1e:80
Signal : 68%
Radio type : 802.11n
Channel : 13
Basic rates (Mbps) : 1 2 5.5 11
Other rates (Mbps) : 6 9 12 18 24 36 48 54


2 使用收集到的 BSSID 发送类似 这个示例 中的请求即可,so easy!

当然, 这只是 online WiFi positioning, 还有更黑科技的 offline positioning,有空再更。



Demo Screenshot

Chian HLS JS API + Global Positioning API

Global HLS Imagery View

Have Fun!

在上一篇中,尝试给出三条备选路线方案,只是看看是不够的,还需要做些交互式的操作支持。

1 Visual Tweaks

首先,这里我们做点小小的视觉调整,让路线和位置更漂亮一点。例如:

给路线的加上半透明效果,在drawRoute()中增加:

1
2
3
var routeLine = new H.map.Polyline(strip, {
style: { strokeColor: 'rgba(0, 85, 170, 0.5)', lineWidth: 3 }
})

再给配送的起点、终点,快递员的当前位置赋予不同的显示符号,在map.js中修改addMarker()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
HEREMap.prototype.addMarker = function (coordinates, icon) {
// Add an markerOptions object
var markerOptions = {}
// three different icons for origin, destination and the user's current position
var icons = {
iceCream: {
url: './images/marker-gelato.svg',
options: {
size: new H.math.Size(26, 34),
anchor: new H.math.Point(14, 34)
}
},
origin: {
url: './images/origin.png',
options: {
size: new H.math.Size(32, 32),
anchor: new H.math.Point(12, 36)
}
},
destination: {
url: './images/destination.png',
options: {
size: new H.math.Size(32, 32),
anchor: new H.math.Point(12, 36)
}
}
}

if (icons[icon]) {
markerOptions = {
icon: new H.map.Icon(icons[icon].url, icons[icon].options)
}
}

var marker = new H.map.Marker(coordinates, markerOptions)
this.map.addObject(marker)

return marker
}

2 Route Selection

在显示的三条路线中,允许用户选择一条作为最佳选项,那么我们需要在onSuccess()中增加一个回调函数来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var onSuccess = function (result) {
if (result.response.route) {
var routeLineGroup = new H.map.Group()

var routes = result.response.route.map(function (route) {
var routeLine = drawRoute(route)
routeLineGroup.addObject(routeLine)

return {
route: route,
routeLine: routeLine
}
})

map.addObject(routeLineGroup)
map.setViewBounds(routeLineGroup.getBounds())

onRouteSelection(routes[0])
}
}

为了在地图上高亮出选中的路线,就需要给Polyline指定不同的制图符号,

1
2
3
4
var routeLineStyles = {
normal: { strokeColor: 'rgba(0, 85, 170, 0.5)', lineWidth: 3 },
selected: { strokeColor: 'rgba(255, 0, 0, 0.7)', lineWidth: 7 }
}

路线选择的目的是为了允许用户用户点选prefer的道路,因此我们需要增加一个函数,允许渲染引擎更改道路的符号,onRouteSelection()

1
2
3
var onRouteSelection = function (route) {
console.log('A route has been selected.', route)
}

为了测试以上逻辑的正确与否,做个简单测试,在onSuccess()中调用onRouteSelection(),并且将返回结果中的第一条路线作为参数传入。

1
2
// pass the first route as argument for temperary testing
onRouteSelection(routes[0])

到这里,你可能会发现被选中的道路可能位于其他道路之下,因此在道路的公共部分出现了叠加的多层效果。为了确保所选道路在所有道路之上显示,我们可以设置下渲染顺序,即 z-index。如下例子中,10 为更接近上层的渲染顺序。

1
2
3
4
5
6
 if (selectedRoute) {
selectedRoute.routeLine.setStyle(routeLineStyles.normal).setZIndex(1)
}
route.routeLine.setStyle(routeLineStyles.selected).setZIndex(10)
selectedRoute = route
}

这样就清爽了很多。

3 Route Selection Panel

这一步我们最后完善与道路的“交互”。 增加一个小的面板指示已经选择的道路。我们将这一部分的功能也放到另外一个独立的脚本文件中,在 scripts目录下新建一个routes_panel.js的文件。当然,不要忘记在 html 页面中增加加载此脚本文件的语句。 <script src="scripts/routes_panel.js"></script>

另外,在html文件中需要增加一个容器来渲染路线选择面板。

1
2
3
<div id="route-panel">
<ul></ul>
</div>

定义 HERERoutesPanel 类,用于渲染路线选择面板中的内容,详见我的GitHub 中 routes_panel.js 的内容。

4 Directions

HERE Routing API 也会返回自然语言形式的导航信息(direction information),继续再 HERERoutesPanel类中增加内容:

1
2
3
4
5
6
7
8
var renderRouteElement = function (route, i) {
var element = document.createElement('li')

var routeSummary = route.route.summary
element.innerHTML = renderRouteTitle(routeSummary, i)

var maneuvers = route.route.leg[0].maneuver
element.innerHTML += renderManeuvers(maneuvers)

上面代码中, leg 是什么? 一个leg代表的是两个 waypoint之间的路线部分,求算一条路线至少要有起点和终点两个 waypoint,其他的waypoint 可以是途经点。见 Developer doc:Waypoints

看起来HERE已经提供了 pre-formatted maneuver instruction string,那么就直接用好了。

1
2
3
4
5
6
7
8
9
var renderManeuvers = function (maneuvers) {
return [
'<ol class="directions">',
maneuvers.map(function (maneuver) {
return '<li>' + maneuver.instruction + '</li>'
}).join(''),
'</ol>'
].join('')
}

这篇Tutorial的原文巨长,看得我一度想放弃,因为最近工作忙碌的关系,断断续续总算看完了; 并且参照原文思路和示例代码review完结。


original resource Part4: Advanced Routing

complete code Github - kikitaMoon/HERE_JS_Who_Wants_Icecream

0%