使用Angular和Ionic创建的地图应用
首先
-
- 11月に入り、Angular v9.0.0-rc.0が出たことで@angular/google-mapsが利用できるようになりました!!?
-
- せっかくなので今回はIonicと@angular/google-mapsを使って地図アプリを作ってみようかと思います。
@angular/google-mapsの使い方に関してはすでにAngular Material に追加された google-maps を使ってみたの方で記事まとめられているので参考にしてみてください。
预先准备
请提前安装 Angular CLI、Ionic 和 Cordova。
npm install -g @angular/cli
npm i -g ionic cordova
创建项目
ionic start map-sample
执行时会提示选择要使用的FW,我们选择Angular。
? Framework: (Use arrow keys)
❯ Angular | https://angular.io
React | https://reactjs.org
有人問起範本,所以我會選擇選項「Tabs」。
? Starter template:
❯ tabs | A starting project with a simple tabbed interface
sidemenu | A starting project with a side menu with navigation in the content area
blank | A blank starter project
my-first-app | An example application that builds a camera with gallery
conference | A kitchen-sink application that shows off all Ionic has to offer
当这样做时,Ionic环境将自动开始构建,所以请稍等直到完成。
完成后,请移动到目录并尝试运行一次!
cd ./map-sample
ionic serve

实施
让我们安装所需的模块吧。首先安装@angular/google-maps,然后进行必要的配置。
npm install @angular/google-maps
<!doctype html>
<head>
...
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY">
</script>
</head>
为了使用谷歌地图,您需要获取一个API密钥,按照此处的指示获取密钥,并将上述index.html文件中的YOUR_API_KEY部分替换掉。
在@angular/google-maps中,可以显示地图、显示标记和显示信息窗口,但无法搜索附近设施或创建路线,因此需要安装@types/googlemaps以使其可用。请提前安装此项。
npm install @types/googlemaps
在tsconfig.app.json文件的types属性中添加googlemaps。
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": ["googlemaps"] // 追加
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/test.ts",
"src/**/*.spec.ts"
]
}
我会先确认能否显示地图。
※由于模板生成时默认创建的文件将直接使用,所以文件名等都是随意的,如果您在意的话,请随意更改为您喜欢的名称。我们将在tab1.module.ts文件中导入GoogleMapsModule。
import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { GoogleMapsModule } from '@angular/google-maps'; // 追加
@NgModule({
imports: [
IonicModule,
CommonModule,
FormsModule,
RouterModule.forChild([{ path: '', component: Tab1Page }]),
GoogleMapsModule // 追加
],
declarations: [Tab1Page]
})
export class Tab1PageModule {}
我将在tab1.page.html中尝试显示地图。
<ion-header>
<ion-toolbar>
<ion-title>
Tab One
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<google-map></google-map>
</ion-content>

我们继续快速地进行实施吧!
尝试实现地点搜索
在地点搜索中,使用google.maps.Geocoder的geocode方法。
在服务类中实现geocode的调用。
首先,创建一个服务类。
ng g s services/map
我将添加以下两个函数。
geocode(
request: google.maps.GeocoderRequest
): Promise<google.maps.GeocoderResult> {
const geocoder = new google.maps.Geocoder();
return new Promise((resolve, reject) => {
geocoder.geocode(request, (result, status) => {
if (this.geocodeResultCheck(status)) {
resolve(result[0]);
} else {
reject(status);
}
});
});
}
private geocodeResultCheck(status: google.maps.GeocoderStatus): boolean {
if (status === google.maps.GeocoderStatus.OK) {
return true;
} else if (status === google.maps.GeocoderStatus.ERROR) {
alert('接続に失敗しました。再度やり直してください。');
} else if (status === google.maps.GeocoderStatus.INVALID_REQUEST) {
alert('リクエストが無効です。');
} else if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
alert('時間をおいて再度やり直してください。');
} else if (status === google.maps.GeocoderStatus.REQUEST_DENIED) {
alert('Mapの利用が許可されていません。');
} else if (status === google.maps.GeocoderStatus.UNKNOWN_ERROR) {
alert('サーバーエラーが発生しました。再度やり直してください。');
} else if (status === google.maps.GeocoderStatus.ZERO_RESULTS) {
alert('見つかりませんでした。検索キーワードに誤字や脱字がないかご確認ください。地名や郵便番号を追加してみてください。');
}
return false;
}
geocode(google.maps.GeocoderRequest)函数接收地点请求,并返回该地点的搜索结果。
geocodeResultCheck(status)函数则根据搜索结果状态进行相应处理。
如果可以的话,接下来我们将在组件一侧实现结果的显示!
在tab1.modules.ts中添加GoogleMapsModule。
import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { GoogleMapsModule } from '@angular/google-maps'; // 追加
@NgModule({
imports: [
IonicModule,
CommonModule,
FormsModule,
RouterModule.forChild([{ path: '', component: Tab1Page }]),
GoogleMapsModule // 追加
],
declarations: [Tab1Page]
})
export class Tab1PageModule {}
我接下来会写出HTML所需要的内容。
我认为只要有地图、输入框和搜索按钮就可以了。
<ion-header>
<ion-toolbar>
<ion-title>
Tab One
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<input type="text" placeholder="マップを検索する" [(ngModel)]="value" />
<button (click)="geocode()">検索</button>
<google-map></google-map>
</ion-content>
当按钮被按下时,我们将使用输入的值调用地理编码,因此将如下写入代码中。
import { Component, ViewChild } from '@angular/core';
import { GoogleMap } from '@angular/google-maps';
import { MapService } from '../services/map.service';
@Component({
selector: 'app-tab1',
templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
@ViewChild(GoogleMap, { static: false }) map!: GoogleMap;
value = '';
constructor(private mapService: MapService) {}
geocode() {
this.mapService
.geocode({ address: this.value })
.then(result => {
this.map.center = result.geometry.location;
})
.catch(() => {
console.log('geocode 失敗');
});
}
}
尝试在执行此操作后,搜索适当的地名,我认为地区地图将显示出来?
尝试实现周边设施搜索。
在搜索周边设施方面,可以使用google.maps.places.PlacesService中的nearbySearch、textSearch、findPlaceFromQuery等方法。
本次将使用nearbySearch进行实现。
我们将在服务类中实现对nearbySearch的调用。
為了使用PlaceService,需要在index.html的設置中添加&libraries=places。因此,需要事先編輯好index.html。
<!doctype html>
<head>
...
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">
</script>
</head>
将以下两个函数添加进去。
nearbySearch(
service: google.maps.places.PlacesService,
request: google.maps.places.PlaceSearchRequest
): Promise<google.maps.places.PlaceResult[]> {
return new Promise((resolve, reject) => {
service.nearbySearch(request, (results, status) => {
if (this.nearbySearchResultCheck(status)) {
resolve(results);
} else {
reject(status);
}
});
});
}
private nearbySearchResultCheck(
status: google.maps.places.PlacesServiceStatus
): boolean {
if (status === google.maps.places.PlacesServiceStatus.OK) {
return true;
} else if (status === google.maps.places.PlacesServiceStatus.NOT_FOUND) {
alert('お探しの周辺施設が見つかりませんでした。');
} else if (
status === google.maps.places.PlacesServiceStatus.UNKNOWN_ERROR
) {
alert('サーバーエラーが発生しました。再度やり直してください。');
} else if (
status === google.maps.places.PlacesServiceStatus.INVALID_REQUEST
) {
alert('リクエストが無効です。');
} else if (
status === google.maps.places.PlacesServiceStatus.OVER_QUERY_LIMIT
) {
alert('時間をおいて再度やり直してください。');
} else if (
status === google.maps.places.PlacesServiceStatus.REQUEST_DENIED
) {
alert('Mapの利用が許可されていません。');
} else if (status === google.maps.places.PlacesServiceStatus.ZERO_RESULTS) {
alert('お探しの周辺施設が見つかりませんでした。');
}
return false;
}
接下来将进行显示的实现!刚刚已经在tab1上实现了,接下来将在tab2上进行实现。
同样,像之前的地点搜索一样,在tab2.modules.ts中添加GoogleMapsModule。
import { IonicModule } from '@ionic/angular';
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Tab1Page } from './tab1.page';
import { GoogleMapsModule } from '@angular/google-maps'; // 追加
@NgModule({
imports: [
IonicModule,
CommonModule,
FormsModule,
RouterModule.forChild([{ path: '', component: Tab1Page }]),
GoogleMapsModule // 追加
],
declarations: [Tab1Page]
})
export class Tab1PageModule {}
将搜索结果中的商店名称以列表形式显示出来。
<ion-header>
<ion-toolbar>
<ion-title>
Tab Two
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<input type="text" placeholder="周辺施設を検索する" [(ngModel)]="value" />
<button (click)="search()">検索</button>
<google-map></google-map>
<div *ngFor="let result of results">
{{ result.name}}
</div>
</ion-content>
import { Component, ViewChild } from '@angular/core';
import { GoogleMap } from '@angular/google-maps';
import { MapService } from '../services/map.service';
@Component({
selector: 'app-tab2',
templateUrl: 'tab2.page.html',
styleUrls: ['tab2.page.scss']
})
export class Tab2Page {
@ViewChild(GoogleMap, { static: false }) map!: GoogleMap;
value = '';
results: google.maps.places.PlaceResult[];
constructor(private mapService: MapService) {}
search() {
this.mapService
.geocode({ address: this.value })
.then(result => {
this.map.center = result.geometry.location;
this.nearbySearch(result.geometry.location);
})
.catch(() => {
console.log('geocode 失敗');
});
}
private nearbySearch(latLng: google.maps.LatLng): void {
const placeService = new google.maps.places.PlacesService(
this.map.data.getMap()
);
const request: google.maps.places.PlaceSearchRequest = {
location: latLng,
radius: 50000,
keyword: `${this.value} コーヒー`
};
this.mapService
.nearbySearch(placeService, request)
.then(results => {
this.results = results;
})
.catch(() => {
console.log('nearbySearch 失敗');
});
}
}
只需要一种选择,我会用中文来释义上述内容:周边设施的搜索需要位置信息(即纬度和经度信息),因此在按下搜索按钮后首先搜索该地点。然后利用这个结果执行附近搜索。顺便提一下,在PlaceSearchRequest中可以使用type来对搜索类型进行筛选,但是由于类型太多,有些难以使用,个人认为直接使用关键词进行搜索更方便。本次使用了“咖啡”作为筛选条件。如果可以动态更改这部分,就可以通过“地点名称”+“类别信息”进行灵活的搜索。我认为这样的搜索与平时使用谷歌地图时差不多。
尝试将其作为iOS和Android应用运行。
如果您想要将它作为本地应用程序运行,只需执行以下命令,即可生成文件。
ionic cordova build ios --prod
ionic cordova build android --prod
在我的环境下,ionic cordova build ios –prod在初始运行时总是会失败…会显示出这样的错误。
** ARCHIVE FAILED **
xcodebuild: Command failed with exit code 65
[ERROR] An error occurred while running subprocess cordova.
cordova build ios exited with exit code 65.
Re-running this command with the --verbose flag may provide more information.
如果出现类似的错误,请先执行后编辑生成的config.xml文件的widget id。
<widget id="jp.test.map" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">

如果能做到这一点,然后再次执行构建,我认为就可以成功完成,而无需出现任何错误消息。
至少在我的环境中,我可以通过这样做来运行成功!
尽管 Android 应用程序也遇到了失败,但是在阅读这篇文章的同时,我成功地解决了问题!
我认为你可以使用Xcode或Android Studio来启动模拟器并进行操作确认。
结束
所以,这次我尝试制作了一个简单的地图应用!即使没有iOS或Android本地应用开发知识,也能制作出应用程序,这真是愉快又好玩呀~
如果方便的话,请参考我在git上提供的这次的源代码!