@ionic Native - Google Maps
@ionic Native - Google Maps
Masashi Katsumata
Ionic framework
@ionic-native/google-maps
● Embed native Google Maps view in your app
● One code for both Android, iOS and Browser (from v4.12.0)
● Easy to use
b e t ween
e
Bridg system
e
nativ ur code!
o
and y
@ionic-native/google-maps
This is native
Google Maps
view, which is not
a HTML element.
u c h layer
to
e)
(nativ
Is this on map
or browser?
Pass
the touch event to
the map or browser
Hello world
API key
Create a project
https://cloud.google.com/maps-platform/#get-started
API key
Enable billing
You need to register your
payment way.
● But no!!
If you run your app on the browser platform, this plugin uses Google Maps
JavaScript API v3.
● Bonus: you can get $200 credit / month for other Maps APIs
(Google Directions API, Google Maps JavaScript API v3, and so on)
API key
This
i
API s your
key
Create a project
$> ionic start myApp sidemenu --type=angular
? Integrate your new app with Cordova to target native iOS and Android? YES
Install cordova-plugin-googlemaps
Edit `config.xml`
<preference
name="GOOGLE_MAPS_ANDROID_API_KEY" value="(api key)" />
<preference
name="GOOGLE_MAPS_IOS_API_KEY" value="(api key)" />
</widget>
API key Create project HTML & CSS
src/app/home/home.page.html
<ion-content padding>
<h3>Ionic GoogleMaps Starter</h3>
<div id="map_canvas">
<button ion-button (click)="onButtonClick($event)">Start demo</button>
</div>
</ion-content> Ad
da
div
(m
ap
div
)
API key Create project HTML & CSS
src/pages/home/home.scss
Background styles become be transparent
#map_canvas { by the maps plugin mandatory!
height: 90%;
} You can set the background color through
Environment.setBackgroundColor()
src/app/home/home.page.ts
import { Component, OnInit } from '@angular/core';
import { Platform } from '@ionic/angular';
import { GoogleMaps, GoogleMap } from '@ionic-native/google-maps/ngx';
map: GoogleMap;
constructor(private platform:Platform) {}
async ngOnInit() {
// you need to wait `platform.ready()`
await this.platform.ready(); Important!!!
await this.loadMap();
}
If you forget that …
ionViewDidLoad() is executed
(DOM elements are ready)
src/app/home/home.page.ts
loadMap() {
// If you want to run your app
// on browser, insert this line.
Environment.setEnv({
'API_KEY_FOR_BROWSER_RELEASE': '',
'API_KEY_FOR_BROWSER_DEBUG':''
});
// Create a map
// after the view is ready
// and the native platform is ready.
this.map = GoogleMaps.create('map_canvas');
}
API key Create project HTML & CSS Coding Run it!
Run it!!
$> ionic cordova run android $> ionic cordova run browser -l
Marker
@ionic-native/google-maps
Add a marker
this.map.addMarker({
title: '@ionic-native/google-maps',
icon: 'blue',
animation: 'DROP',
position: {
lat: 43.0741904,
lng: -89.3809802
}
}).then((marker: Marker) => {
marker.showInfoWindow();
});
Add a marker from v4.8.2 -
marker.showInfoWindow();
icon property
this.map.addMarkerSync({ color name : blue, red, green, yellow ....
title: '@ionic-native/google-maps', (157 color names are defined in this plugin)
icon: 'blue',
animation: 'DROP', rgb(), rgba() , hsl(), hsla(), #RGB, #RGBA
position: { :
./assets/icon.png (jpg, gif, and png) :
lat: 43.0741904,
lng: -89.3809802 http(s)://yourserver/icon.png :
}
}); cdvfile:// …. /icon.png :
data:image/png;base64,iVBORw0KGgo...CC :
:
icon property
let POINTS: BaseArrayClass<any> = new BaseArrayClass<any>([
{
position: {lat:41.79883, lng:140.75675},
iconData: "./assets/imgs/Number-1-icon.png"
},
{
position: {lat:41.799240000000005, lng:140.75875000000002},
iconData: "http://icons.iconarchive.com/.../24/Number-2-icon.png"
},
{
position: {lat:41.797650000000004, lng:140.75905},
iconData: {
url: "http://icons.iconarchive.com/.../48/Number-3-icon.png",
size: { width: 24, height: 24}
}
},
{
position: {lat:41.79637, lng:140.76018000000002},
title: "4",
iconData: "blue"
},
{
position: {lat:41.79567, lng:140.75845},
title: "5",
iconData: "data:image/png;base64,iVBORw...CC"
}
]);
Marker events
● MARKER_CLICK
● MARKER_DRAG_START
● MARKER_DRAG
● MARKER_DRAG_END
● INFO_CLICK
● INFO_LONG_CLICK
● INFO_OPEN
● INFO_CLOSE
Add event listener
Listen the event only one time
marker.addEventListenerOnce(GoogleMapsEvent.MARKER_CLICK).then();
// Alias method
marker.one(GoogleMapsEvent.MARKER_CLICK).then();
// Alias method
marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe();
Remove event listener
Listen the event only one time
// Remove particular event listener
marker.off(GoogleMapsEvent.MARKER_CLICK, this.onMarkerClick);
this.map.addMarker({
position: {
lat: 43.0741804,
lng: -89.382
},
title: "B",
disableAutoPan: true
}).then(this.onMarkerAdded);
onMarkerAdded(marker: Marker) {
marker.one(GoogleMapsEvent.MARKER_CLICK).then(() => {
alert("Marker" + marker.getTitle() + " is clicked");
});
}
Polyline
@ionic-native/google-maps
Polyline
let AIR_PORTS = [
HND_AIR_PORT, HNL_AIR_PORT, SFO_AIR_PORT
];
this.map.addPolyline({
points: AIR_PORTS, Click e
ve
color: '#AA00FF', (Becaus nt with LatL
e this ng
width: 10, plugin
calcula
geodesic: true, tes own
way)
clickable: true // clickable = false in default
}).then((polyline: Polyline) => {
polyline.on(GoogleMapsEvent.POLYLINE_CLICK).subscribe((params: any) => {
let position: LatLng = <LatLng>params[0];
this.map.addMarker({
position: position,
title: position.toUrlValue(),
disableAutoPan: true
}).then((marker: Marker) => {
marker.showInfoWindow();
});
});
});
Polyline from v4.8.2 -
let AIR_PORTS = [
HND_AIR_PORT, HNL_AIR_PORT, SFO_AIR_PORT
];
@ionic-native/google-maps
Polygon
let GORYOKAKU_POINTS: ILatLng[] = [
{lat: 41.79883, lng: 140.75675},
{lat: 41.799240000000005, lng: 140.75875000000002},
{lat: 41.797650000000004, lng: 140.75905},
…
{lat: 41.79909000000001, lng: 140.75465}
]; ng[]
s I LatL
pas
this.map.addPolygon({ Just
'points':GORYOKAKU_POINTS,
'strokeColor' : '#AA00FF',
'fillColor' : '#00FFAA',
'strokeWidth': 10
}.then((polygon: Polygon) => {
...
});
Polygon from v4.8.2 -
@ionic-native/google-maps
Circle
let center: ILatLng = {"lat": 32, "lng": -97};
let radius = 300; // radius (meter)
this.map.addCircle({
'center': center,
'radius': radius,
'strokeColor' : '#AA00FF',
'strokeWidth': 5,
'fillColor' : '#00880055'
}).then((circle: Circle) => {
circle.setRadius(newRadius);
});
});
Circle from v4.8.2 -
circle.setRadius(newRadius);
});
GroundOverlay
@ionic-native/google-maps
GroundOverlay
this.map.one(GoogleMapsEvent.MAP_READY).then(() => {
return this.map.addGroundOverlay({
'url': 'assets/imgs/newark_nj_1922.jpg',
'bounds': bounds,
'opacity': 0.5,
'clickable': true // default = false
});
}).then((groundOverlay: GroundOverlay) => {
});
GroundOverlay from v4.8.2 -
@ionic-native/google-maps
TileOverlay
You can
generat
e vario
us URL
this.map.addTileOverlay({
getTile: (x: number, y: number, zoom: number) => {
return "http://tile.stamen.com/watercolor/" +
zoom + "/" + x + "/" + y + ".jpg";
},
opacity: 1.0
});
TileOverlay from v4.8.2 -
opacity: 1.0
});
HtmlInfoWindow
@ionic-native/google-maps
HtmlInfoWindow(1)
let htmlInfoWindow = new HtmlInfoWindow();
let frame: HTMLElement = document.createElement('div');
frame.innerHTML = [
'<h3>Hearst Castle</h3>',
'<img src="assets/imgs/hearst_castle.jpg">'
].join("");
frame.getElementsByTagName("img")[0].addEventListener("click", () => {
htmlInfoWindow.setBackgroundColor('red');
});
htmlInfoWindow.setContent(frame, {width: "280px", height: "330px"});
this.map.addMarker({
position: {lat: 35.685208, lng: -121.168225},
draggable: true,
disableAutoPan: true
}).then((marker: Marker) => {
marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe(() => {
htmlInfoWindow.open(marker);
});
});
HtmlInfoWindow(2) Full source code
onMarkerClick(params: any[]) {
// Get a marker instance from the passed parameters
let marker: Marker = params.pop();
// Create a component
const compFactory = this.resolver.resolveComponentFactory(CustomTag);
let compRef: ComponentRef<CustomTag> =
compFactory.create(this.injector);
compRef.instance.myTitle = marker.get('myTitle');
this.appRef.attachView(compRef.hostView);
// Dynamic rendering
this._ngZone.run(() => {
this.htmInfoWindow.setContent(div);
this.htmInfoWindow.open(marker);
});
}
Marker cluster
@ionic-native/google-maps
MarkerCluster
this.map.addMarkerCluster({
markers: data,
icons: [
{
min: 3, max: 9,
url: "./assets/markercluster/small.png",
label: { color: "white" }
},
{
min: 10,
url: "./assets/markercluster/large.png",
label: { color: "white" }
}
]
}).then((markerCluster: MarkerCluster) => {
markerCluster.on(GoogleMapsEvent.MARKER_CLICK).subscribe((params) => {
let marker: Marker = params[1];
marker.setTitle(marker.get("name"));
marker.setSnippet(marker.get("address"));
marker.showInfoWindow();
});
});
MarkerCluster from v4.8.2 -
markerCluster.on(GoogleMapsEvent.MARKER_CLICK).subscribe((params) => {
});
Geocoding
@ionic-native/google-maps
Geocoding
ess
// Address -> latitude,longitude addr
one
g for
Geocoder.geocode({ odin
Geoc
"address": this.search_address
})
.then((results: GeocoderResult[]) => {
console.log(results);
return this.map1.addMarker({
'position': results[0].position,
'title': JSON.stringify(results[0].position)
})
})
.then(...)
Batch geocoding rray
n s as a
Geocoder.geocode({ tio
loca
Pass
// US Capital cities
"address": [
"Montgomery, AL, USA", "Juneau, AK, USA", ...
"Madison, WI, USA", "Cheyenne, Wyoming, USA"
]
})
.then((mvcArray: BaseArrayClass<GeocoderResult[]>) => {
Get
}); a
then mvc ar
r
noti `finish ay firs
fied ` ev t
. ent ,
is
Just
1.9 s
ec!
BaseClass
@ionic-native/google-maps
All classes extend Base class
BaseClass
BaseClass
● set()
● get()
● bindTo()
● trigger()
● on() / addEventListener()
● one() / addEventListenerOnce()
● off()
● empty()
● destroy()
set() and get() obj.set(key, value, noNotify?)
obj.get(key)
myObj.set("hello", "world");
"Hel
lo_c
console.log(myObj.get("hello")); hang
ed"
even
t oc
curs
(key)_changed event
let myObj: BaseClass = new BaseClass();
myObj.on("hello_changed").subscribe((params) =>an{ged
h
console.log(params); o_c
hell
});
myObj.set("hello", "world");
myObj.set("hello", "world2"); hello_c
hange
d
Status change event anged
_ch
ition
pos
marker.setPosition(...);
title_changed
marker.setTitle("....");
Own property
this.map.addMarker({
position: { lat: 43.0741704, lng: -89.3809802},
count: 0
})
.then((marker: Marker) => {
marker.on(GoogleMapsEvent.MARKER_CLICK).subscribe(() => {
marker.set("count", marker.get("count") + 1);
});
});
Own property from v4.8.2 -
myObj.on("hello_changed").subscribe((params) => { DO N
OT
console.log(params); OCC
UR
}); hello_changed
objA.set("hello", "こんにちは");
objB.get("world");
objA objB
hello world
"こんにちは" "こんにちは"
bindTo()
this.map.addMarker({
position: {lat: 43.0741704, lng: -89.3809802},
draggable: true
})
.then((marker: Marker) => {
this.map.addCircle({
center: marker.getPosition(),
radius: 10,
fillColor: "rgba(0, 0, 255, 0.5)",
strokeColor: "rgba(0, 0, 255, 0.75)",
strokeWidth: 1
}).then((circle: Circle) => {
marker.bindTo("position", circle, "center");
});
});
bindTo() from v4.8.2 -
createMarkers() {
let bounds = this.map.getVisibleRegion();
let sw = bounds.southwest, ne = bounds.northeast;
let diffY = (ne.lat - sw.lat), diffX = (ne.lng - sw.lng);
for (let i = 0; i < 10; i++) {
this.map.addMarker({
'position': {
'lat': sw.lat + diffY * Math.random(),
'lng': sw.lng + diffX * Math.random()
}
}).then((marker:Marker) => {
this.map.on('animate').subscribe((params: []) => {
let animation: string = params[0];
marker.setAnimation(animation);
});
});
}
}
onButtonClick() {
let btnTxt: string = event.srcElement.innerText;
this.map.trigger("animate", btnTxt);
}
Deep understanding
Internal command queue
Cordova executes all methods in asynchronously!
this.map.addMarker({ .then((marker:Marker) =>
… {
})
});
exec("Map", "loadPlugin",
"Marker");
map.addMarker()
(native code)
What would be occurred like this code?
this.map.clear();
this.map.addMarker({
position: positions[i]
}).then((marker: Marker) => {
this.markers.push(marker);
});
}
map.clear() is slow...
Correct way:
this.map.clear();
this.map.clear().then(() => {
for (let i = 0; i < positions.length; i++) { ….
});
this.map.addMarker({
position: positions[i]
}).then((marker: Marker) => {
this.markers.push(marker);
});
}
Don’t worry, this plugin handles correctly!
this.map.clear();
this.map.addMarker({
position: positions[i]
}).then((marker: Marker) => {
this.markers.push(marker);
});
}
Because this plugin has own command queue!
Execute method in synchronous
map.clear()
(Stop all other methods)
marker.showInfoWindow();
MAP_READY event
Used be ...
this.map = GoogleMaps.create('map_canvas');
this.map.one(GoogleMapsEvent.MAP_READY).then(() => {
this.map.addMarker({
position: {lat: …, lng: …},
title: "Hello world"
});
});
from v4.8.2 -
this.map = GoogleMaps.create('map_canvas');
console.log(visibleRegion); // == null
ionViewDidLoad() {
this.loadPanorama();
}
loadPanorama() {
}
}
BaseArrayClass
@ionic-native/google-maps
BaseArrayClass
● map() ● indexOf() ● insertAt()
● filter() ● removeAt()
● filterAsync() ● getLength()
● reverse()
● sort()
forEach() baseArray.forEach(fn)
0 1 2 3 4
forEach() baseArray.forEach(fn)
this.map.addMarker({
position: position
}).then((marker: Marker) => {
this.markers.push(marker);
});
});
forEachAsync() baseArray.forEachAsync(fn).then()
Async task
(i.e. setTimeout())
0 1 2 3 4
forEachAsync() baseArray.forEachAsync(fn).then()
this.map.addMarker({
position: position
}).then((marker: Marker) => {
this.markers.push(marker);
next();
});
}.then(() => {
console.log('finish!');
});
map() baseArray.map(fn)
0 1 2 3 4
a b c d e
a b c d e
map() baseArray.map(fn)
return marker.getTitle();
});
mapAsync() baseArray.mapAsync(fn).then()
0 1 2 3 4 Async task
(i.e. setTimeout())
a b c d e
a b c d e
mapAsync() baseArray.mapAsync(fn).then()
this.map.addMarker({
position: position
}).then(next);
0 1 2 3 4
a c e
filter() baseArray.filter(fn)
});
filterAsync() baseArray.filterAsync(fn).then()
0 1 2 3 4 Async task
(i.e. setTimeout())
a c e
filterAsync() baseArray.filterAsync(fn).then()
at event
insert_
baseArray.push('a');
baseArray.push('b');
baseArray.push('c');
remove_at event
baseArray.pop();
baseArray.pop();
baseArray.pop();
BaseArrayClass example
let points: ILatLng[] = [
{lat: 33.91636924837674, lng: -118.39605331420898},
{lat: 33.90205144970967, lng: -118.39639663696288},
{lat: 33.90190897196702, lng: -118.37905883789062},
{lat: 33.89471353635718, lng: -118.3787155151367}
];
this.map = this.googleMaps.create('map_canvas', {
camera: {
target: points
}
});
this.map.one(GoogleMapsEvent.MAP_READY).then(() => {
return this.map.addPolyline({
points: points
});
})
@ionic-native/google-maps
KML overlay
this.loading = this.loadingCtrl.create({
content: 'Please wait...'
});
this.loading.present();
this.map.addKmlOverlay({
url: "assets/kmloverlay/polygon.kml"
}).then((kmlOverlay: KmlOverlay) => { Fits cam
era
this.loading.dismiss(); to view
kml
overall
console.log(kmlOverlay);
this.map.moveCamera(kmlOverlay.getDefaultViewport());
});
LocationService
@ionic-native/google-maps
LocationService
let option: MyLocationOptions = {
// true: use GPS as much as possible (use battery usage alot)
//
// false: use network location or lastKnownLocation
// (save battery usage)
enableHighAccuracy: true
};
...
}).catch((error: any) => {
// Can not get location, permission refused, and so on...
console.log(error);
});
Good example projects
In this case, ionic keeps the first page but css visibility = hidden.
The maps plugin can detect this particular case, and detach the main
page map automatically.
Then create another map for detail page.
After closing the second page, there is no more div element for the
second map. So the maps plugin destroys the second map automatically.
Then the maps plugin attaches the map view to browser automatically,
when the main page becomes visibility = visible.
Plugin repository
https://github.com/mapsplugin/
cordova-plugin-googlemaps
Cordova GoogleMaps plugin
author Official documents
https://github.com/ionic-team/ionic-n
& ative-google-maps/blob/master/docu
ments/README.md
Google Developers Expert of
Google Maps API Code repository
https://github.com/mapsplugin/ionic-
Masashi Katsumata googlemaps-quickdemo