kikita & Maps

GIS,spatial and artificial intellegence learning and share

在 Part 3 中我们了解到如何调用 HERE Routing API 实现两点间路径规划,以及如何从当前位置出发规划路径。

这个部分继续来看看,如何展示多条路线及其信息,并允许用户交互选择。

Pre-session: 重构代码

这一部分不是改进功能,而是整理思绪。磨刀不误砍柴工,放之四海而皆准。

冰淇凌小网页的功能不断增多,代码也随之膨胀,我们要时不时地整理收纳一下,避免自己在找乐子的路上备受打击。

在 Scripts 文件夹中,新建:

  • map.js 用于存放和底图绘制路线相关的逻辑;
  • utils.js 用于存放辅助功能,例如,将 latlog 坐标转化成 WayPointString 以供 Routing Service 使用。

代码与Part 3 类似,但是因为做了重构,需要注意其中的调用关系及 html页面中的加载顺序。

complete code Github - kikitaMoon/HERE_JS_Who_Wants_Icecream

Multiple Routes

在日常导航出行时,多给出几条路线供用户选择,也是一个极常见的需求。

使用 HERE Routing API 计算路线时,可在请求中加入参数 alternatives 来获得多个推荐路线。

alternatives: 2; 即,1 (primary) + 2 (alternatives) = 3 条路线。

1
2
3
4
5
6
7
8
9
10
11
HEREMap.prototype.drawRoute = function (fromCoordinates, toCoordinates) {
var routeOptions = {
mode: 'fastest;car',
representation: 'display',
# add parameter
alternatives: 2,
waypoint0: Utils.locationToWaypointString(fromCoordinates),
waypoint1: Utils.locationToWaypointString(toCoordinates)
};
this.routes = new HERERoute(this.map, this.platform, routeOptions);
};

由于产生了多条道路,绘制道路的过程就要被执行多次,那么我们就把 drawRoute 函数挪到 route.js 中的 HERERoute 对象中,以供每次绘制道路调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Add drawRoute Function to HERERoute
var drawRoute = function (route) {
var routeShape = route.shape;
var LineString = new H.geo.LineString();

routeShape.forEach(function (point) {
var parts = point.split(',');
LineString.pushLatLngAlt(parts[0], parts[1]);
});
var routeLine = new H.map.Polyline(LineString, {
style: {
strokeColor: 'blue',
lineWidth: 3
}
});
map.addObject(routeLine);
// map.setViewBounds(routeLine.getBounds());
};

与此同时,做 onSuccess函数的对应修改,在返回结果中依次调用 drawRoute

1
2
3
4
5
6
7
8
var onSuccess = function (result) {
// Simplify on success function
if (result.response.route) {
var routes = result.response.route;
Setting view bounds
routes.forEach(drawRoute);
}
};

Bingo!

View Bounds

基本功能已实现,为了更好的体验,我们继续加工加工。

在 Part 3 中的我们通过 setViewBounds 将地图显示为路线的范围,在Part 4 中显然就不适用了,因为这样只会返回跳转到最后一条路线的范围。将所有的路线加入到一个组中,然后跳转组的范围,来解决这个问题。

onSuccess 进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var onSuccess = function (result) {
if (result.response.route) {
var routes = result.response.route;
// Setting view bounds
// routes.forEach(drawRoute);
// add all routes to a group
var routeLines = routes.map(drawRoute);
var routeLineGroup = new H.map.Group({
objects: routeLines
});
map.addObject(routeLineGroup);
map.setViewBounds(routeLineGroup.getBounds());
}
};

同时移除 drawRoute 中原先的 setViewBounds语句,并向 H.map.Group 容器中增加返回的route。

1
2
3
4
// Setting view bounds using Group bounds not the last route
// map.addObject(routeLine);
// map.setViewBounds(routeLine.getBounds());
return routeLine;

original resource Part4: Advanced Routing

complete code Github - kikitaMoon/HERE_JS_Who_Wants_Icecream

Part 1 中我们准备了底图,Part 2 中我们获取了位置,现在终于开张了,“您有新的 ‘ 饿了乎 ’ 订单到了!” 我们给外卖小哥规划下路线吧!

1 Routing Service

首先,HERE Map API 中提供了一个叫做 Platform 的类,实例化Platform后,我就可以借以访问 HERE Routing Service。 有关 Routing API 的帮助文档,点这里

1
2
3
4
5
6
var platform = new H.service.Platform({
app_id: '[YOUR APP ID HERE]',
app_code: '[YOUR APP CODE HERE]'
});

var router = platform.getRoutingService();

2 Request Route

在发请求之前,需要先至少定义起点和终点,或者必要的时候增加途经点,这些点是通过 WayPointParameterType 对象传递。

为了让代码更简洁易维护,我们在 scripts 目录下创建一个 route.js文件,取代开始直接在 app.js中直接调用 Routing Service 的繁琐。如下,在 route.js 中增加一个用于封装 底图、platform对象、路径规划参数的 HERERoute函数,这个函数也会在浏览器控制台返回简单的错误日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// build a function to take the map and platform objects as parameters
function HERERoute (map, platform, routeOptions) {
// access the routing service by calling getRoutingService()
var router = platform.getRoutingService();
// error handling
var onSuccess = function(result) {
console.log('Route found!', result);
};
var onError = function(error) {
console.error('Oh no! There was some communication error!', error);
}

router.calculateRoute(routeOptions, onSuccess, onError);
}

3 Drawing the Route

现在我们可以在控制台看到 response,如何将 route 绘制到地图上呢

在成功返回结果的函数中增加如下代码,其中 Option_2 的部分用于实现向地图绘制路径:

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
var onSuccess = function (result) {

// Option_1 Simply put the response to the console log
console.log('Route found!', result);

// Option_2 Furtherly draw the response to the map
var route,
routeShape,
startPoint,
endPoint,
lineString;

if (result.response.route) {

// Just take the first Route in the array
route = result.response.route[0];
routeShape = route.shape;

// H.geo.LineString is recommended, instead of H.geo.Strip in the offical demo code
lineString = new H.geo.LineString();
routeShape.forEach(function (point) {
var parts = point.split(',');
lineString.pushLatLngAlt(parts[0], parts[1]);
});

// Utilize H.map.Polyline to draw a blue line
var routeLine = new H.map.Polyline(lineString, {
style: {
strokeColor: 'blue',
lineWidth: 10
}
});
map.addObject(routeLine);

// update the map bounds to that of our route
map.setViewBounds(routeLine.getBounds());
}
};

备注

  • 原文写得早一些,示例代码中 H.geo.Strip类已经从 HERE Maps JS API v3.0.15.0 及之后弃用了,需要使用 H.geo.LineString 类来替换。详情点 这里
  • 如上代码是我修改后的版本。

4 More

到 Steps 3 路径规划的功能基本实现了,Step 4 再增加一个获取当前位置作为起点,并将指定位置作为终点的功能吧。

例如,外卖小哥要给颐和园逛公园的小朋友送去冰凉可口的冰激凌……

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
// a small helper which converts a location into a waypoint
function locationToWaypointString(coordinates) {
return 'geo!' + coordinates.lat + ',' + coordinates.lng;
}

// set a flag called RouteRendered
// to ensure that the route is only rendered the first time
var routeRendered = false;

function updatePosition(event) {
var coordinates = {
lat: event.coords.latitude,
lng: event.coords.longitude
};

var marker = new H.map.Marker(coordinates);
map.addObject(marker);

// If the route has not been rendered yet,
// calculate and render it
if (!routeRendered) {
var route = new HERERoute(map, platform, {
mode: 'fastest;car',
representation: 'display',
waypoint0: locationToWaypointString(coordinates),
waypoint1: locationToWaypointString(EndCoordinates)
});
routeRendered = true;
}
}

// request the user's current location
navigator.geolocation.watchPosition(updatePosition);

结果大概是这样:

由于相关法律的限制,HERE Developer 网站中提供免费试用的中国境内的 HERE 在线地图是非常粗略的等级(Entry Level),如下代码仅为了展示功能,并不为了较真位置的精确程度。

当然,精密级别的 China HERE Map 也已经上线了 ,并且有 China Spec HLS API 可供使用,只是没有默认包含在Global Freemium licenses 之中。如果需要试用评估或购买,需要通过 商务途径;也可以留言我,我也愿意帮助联系。

original resource Part3: Basic Routing

complete code Github - kikitaMoon/HERE_JS_Who_Wants_Icecream

在 Part 1 地图底图准备好的基础之上,我们增加一个获取浏览器当前位置并持续更新的功能,来解决“Where am I ?”的问题。

具体可以通过浏览器的 Geolocation API 来实现。在app.js 脚本中,使用 navigator.geolocation 对象来访问此API。 navigator.geolocation 对象有这样几个方法:getCurrentPosition()watchPosition()clearWatch(), 可以帮助我们实现开始的需求。

1 获取位置

首先,通过 getCurrentPosition() 一次性获取浏览器当前位置,在 app.js 中使用如下代码

1
navigator.geolocation.getCurrentPosition(success[, error[, options]]);

各个浏览器都有 Geolocation API 的支持,建议参考相关的帮助文档。这个Demo建议在 Firefox 中测试,因为Chrome似乎禁用从本地文件访问位置。你可以根据需要增加获取位置是否成功后的行为,详情查看 Firefox的开发者文档

2 持续更新位置

watchPosition() 用于持续“关注” 客户端位置的变化,在每次变化之后,都会将新的位置返回。在 app.js 中增加如下代码,用于在当前位置不断地显示“冰激凌”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function updatePosition (event) {
var coordinates = {
lat: event.coords.latitude,
lng: event.coords.longitude
};
var iconUrl = './images/icecream.svg';
var iconOptions = {
size: new H.math.Size(26, 34),
anchor: new H.math.Point(14, 34)
};
var markerOptions = {
icon: new H.map.Icon(iconUrl, iconOptions)
};
var marker = new H.map.Marker(coordinates, markerOptions);
map.addObject(marker);
map.setCenter(coordinates);
}
// Action
navigator.geolocation.watchPosition(updatePosition);

original resource Part2: Geolocation

complete code Github - kikitaMoon/HERE_JS_Who_Wants_Icecream

Quick Start for HERE Map Javascript API

HERE 官网有一个很有趣的Tutorial, Who wants ice cream !?

如果你注册了HERE Developer 账号,很可能会被推送到这个教程的小广告。

假设你是冰激凌店的老板,那么用HERE Map 做个交互式的地图小网页,边测试边记录。

Let’s get our hands dirty……

1. 准备资源

准备如下结构的目录和文件:

1
2
3
4
5
6
7
8
WhoWantsIceCream
|--base_map_set_up.html
|--scripts
|--app.js
|--images
|--icecream.svg
|--styles
|--main.css

2. 设计页面

HTML 的 head 中引用 HERE Map JS API 以及必要的 CSS;

1
2
3
4
5
6
7
8
9
10
11
<title> Who Want's Icecream ? </title>

<!-- HERE Javascript SDK modules -->
<link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.0/mapsjs-ui.css" />
<script type="text/javascript" charset="UTF-8" src="https://js.api.here.com/v3/3.0/mapsjs-core.js"></script>
<script type="text/javascript" charset="UTF-8" src="https://js.api.here.com/v3/3.0/mapsjs-service.js"></script>
<script type="text/javascript" charset="UTF-8" src="https://js.api.here.com/v3/3.0/mapsjs-ui.js"></script>
<script type="text/javascript" charset="UTF-8" src="https://js.api.here.com/v3/3.0/mapsjs-mapevents.js"></script>

<!-- Styles -->
<link rel="stylesheet" href="styles/main.css">

HTML的 body 中需要至少包含一个div ,用于作为 地图显示的容器; 以及引用js来完成 map 的规定动作。

1
2
3
4
5
<!-- Container for the map -->
<div id="map-container" class="map"></div>
<!-- Scripts -->
<script src="scripts/app.js"></script>

3. 交互式地图底图

增加如下内容到 app.js

1 从HERE Developer 网站申请账号,获取 APPID 和 APPCODE ,在初始化服务时使用。

1
2
3
4
5
6
7
8
// Get Map Container Element
var mapContainer = document.getElementById('map-container');

// Specify the APPID & APPCODE
var platform = new H.service.Platform({
app_id: 'oyNWTb---PMlrXxCH', // // <-- ENTER YOUR APP ID HERE
app_code: '_W_7qfgp-----tQQKkbLA', // <-- ENTER YOUR APP CODE HERE
});

2 创建 Map 对象,并指定初始化时的位置、显示级别、底图样式…… everything as you wish……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ----------------------------------------------------------------------- 
// Displaying a Basic Map, Initialize Map Object
// Create Defaulr Layer
var defaultLayers = platform.createDefaultLayers();

// Adjust the Map Center and the Initial Zoom Level
var coordinates = {
lat: 52.530974, // HERE HQ in Berlin, Germany
lng: 13.384944
};
var mapOptions = {
center: coordinates,
zoom: 14
}
var map = new H.Map(
mapContainer,
defaultLayers.normal.map,
mapOptions);

3 让地图动起来,至少可以平移缩放,很简单的一段即可。

1
2
3
// ----------------------------------------------------------------------- 
// Interacting with the map, at least move around and zoom in / out
var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

4 给地图中心加个图标, 换成一个自定义的也成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// -----------------------------------------------------------------------
// Adding Markers to the Map
var marker = new H.map.Marker(coordinates);

// Custom Icons for Markers
var iconUrl = './images/icecream.svg';

var iconOptions = {
// The icon's size in pixel:
size: new H.math.Size(26, 34),
// The anchorage point in pixel,
// defaults to bottom-center
anchor: new H.math.Point(14, 34)
};

var markerOptions = {
icon: new H.map.Icon(iconUrl, iconOptions)
};

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

TIPS 加个监听,可以在你改变窗口尺寸的时候,自动更新地图的尺寸。

1
2
3
4
5
// ----------------------------------------------------------------------- 
// Set Event Listener to Make Map React Properly
window.addEventListener('resize', function () {
map.getViewPort().resize();
});

这就完成 Interactive Basemap 了,Have fun!

original resource Part1: Basic Map Set-up

complete code Github - kikitaMoon/HERE_JS_Who_Wants_Icecream

因为工作的事情,最近忙于切换节奏适应新环境,有一个月没来 kikitaMap “巡检”了。果然昨天发现了小故障,七牛云存储的存储空间的临时域名失效了,之上的我的图床也就挂了。涉及到的对象:碎碎念相机里 中的全部图片和 木工开物 的小部分博客中图片。Just too bad …

解决方法建议

这种情况下只能发工单联系技术支持了,七牛的技术支持回复比较及时,给出了解决方案,如下:

有两种方式来获取文件:

  1. 您需要先新建一个同区域存储空间,会分配一个新的测试域名到新空间。
    通过qshell batchcopy 到有域名的同区域空间然后再进行qdownload下载操作
    1)qshell listbucket 原bucket名 list.txt
    ​ (list出全部文件,listbucket的文档

2)cat list.txt | awk '{print $1}' >list_final.txt
​ (用awk获取list结果的第一列)
3)qshell batchcopy 原bucket名 新bucket名 list_final.txt
​ (复制到新bucket的文件和原bucket文件名一致,batchcopy的文档
4)qshell qdownload newfilelist.txt
​ (newfilelist.txt为下载的配置文档

qshell安装包及文档请参考
https://developer.qiniu.com/kodo/tools/1302/qshell

如果您不熟悉命令行工具的安装使用,也可以结合文档最后提供的视频教程
https://developer.qiniu.com/kodo/tools/1302/qshell#9

2.
使用工具qrsctl
https://developer.qiniu.com/kodo/tools/1300/qrsctl
qrsctl get <bucket> <file> <file>

实践过程

选择了第一种方式,问题解决,因为看起来像是批量的。

按照提示下载的zip包类似 qshell-v2.2.0.zip 这样的名字,里面包含了Windows,Linux,Mac各操作系统使用的程序。

我以Mac为例,使用Mac自带的 Commandline 访问解压之后的文件夹 qshell-v2.2.0。

1
2
3
4
kikitamoondeMBP:SoftwareSetup kikitamoon$ cd qshell-v2.2.0
kikitamoondeMBP:qshell-v2.2.0 kikitamoon$ ls
qshell-darwin-x64 qshell-linux-x64 qshell-windows-x64.exe
qshell-linux-arm qshell-linux-x86 qshell-windows-x86.exe

其中 qshell-darwin-x64 是Mac可用的 Qshell 版本。在使用Qshell之前需要配置账户信息,qshell account即可设置AK和SK。

1
2
3
4
5
kikitamoon$ ./qshell-darwin-x64
Use help or help [cmd1 [cmd2 [cmd3 ...]]] to see supported commands.
kikitamoon$ ./qshell-darwin-x64 account
Open account file error, open /Users/kikitamoon/.qshell/account.json: no such file or directory, please use `account` to set AccessKey and SecretKey first
kikitamoon$ ./qshell-darwin-x64 account <AK> <SK>

接着使用七牛技术支持给出的建议,将旧库的文件list出来,然后batchcopy到新库中。

1
2
3
kikitamoon$ ./qshell-darwin-x64 listbucket kikitamapgallery list.txt
kikitamoon$ cat list.txt | awk '{print $1}' >list_final.txt
kikitamoon$ ./qshell-darwin-x64 batchcopy kikitamapgallery kikitamapgallerycopy list_final.txt

最后,通过 qdownload 命令下载所有源文件。在执行命令之前还需要做个配置文件,download.conf ; 并且可以设置开几个任务并行下载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
kikitamoon$ ./qshell-darwin-x64 qdownload 4 download.conf

Writing download log to file /Users/kikitamoon/.qshell/qdownload/7d41b43ca364d28da34dbd030f1331be/7d41b43ca364d28da34dbd030f1331be.log
Downloading MGKW_ArcGIS_Daemon.png [1/62, 1.6%] ...
Downloading MGKW_ArcMapStartupLog.png [2/62, 3.2%] ...
Downloading MGKW_BatchClip.png [3/62, 4.8%] ...
Downloading MGKW_FeatureTable.png [4/62, 6.5%] ...
Downloading MGKW_HelloArcGISPythonAPI.png [5/62, 8.1%] ...
...
...
...
Downloading XJL_TripToGreece.png [61/62, 98.4%] ...
Downloading XJL_TripToHongkong.png [62/62, 100.0%] ...
See download log at path /Users/kikitamoon/.qshell/qdownload/7d41b43ca364d28da34dbd030f1331be/7d41b43ca364d28da34dbd030f1331be.log

kikitamoon$

至此,源图片文件算是都找回来了。当然我还需要把我的网站中使用到旧地址的位置update一下喽。

0%