Welcome folks today in this blog post we will be displaying the google maps inside the browser and also we will be having the input field to search for google places and locations and also we will be placing markers inside the map in angular 14 using google places autocomplete api in typescript. All the full source code of the application is shown below.
Get Started
In order to get started you need to initialize a new angular project using the below command as shown below
ng new sampleapp
cd sampleapp
And now you need to install the below libraries as shown below
npm i @turf/turf
And now guys you need to first of all render the google map
inside the angular app. For this you need to create a google cloud console
account and enable the google places
autocomplete api inside the console. And then you need to create a google api key and copy to clipboard as shown below
Directory Structure of Angular Project
Now we will be seeing the different files and folders present inside the angular project as shown below
Now first of all we will be defining the app.module.ts
file and copy paste the below code
app.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { ApiService } from './api.service'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], providers: [ ApiService, ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule {} |
As you can see we are importing the formsModule
into the imports array because we will be using the input
field to get the user location which will be entered by the user. And also inside the providers array we are importing the API
service where we will be writing the code which will communicate with google places autocomplete api to fetch the location and display it inside the google maps.
Writing the Google Autocomplete Service
Now we will be writing the google maps and autocomplete locations api service code. Just create the file called api.service.ts
and copy paste the following code
`
api.service.ts
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 40 41 42 43 44 |
import { Injectable } from '@angular/core'; const GOOGLE_MAPS_API_KEY = 'AIzaSyCaKbVhcX_22R_pRKDYuNA7vox-PtGaDkI'; export type Maps = typeof google.maps; @Injectable() export class ApiService { public readonly api = this.load(); private load(): Promise<Maps> { const script = document.createElement('script'); script.type = 'text/javascript'; script.async = true; script.defer = true; // tslint:disable-next-line:no-bitwise const callbackName = `GooglePlaces_cb_` + ((Math.random() * 1e9) >>> 0); script.src = this.getScriptSrc(callbackName); interface MyWindow { [name: string]: Function; }; const myWindow: MyWindow = window as any; const promise = new Promise((resolve, reject) => { myWindow[callbackName] = resolve; script.onerror = reject; }); document.body.appendChild(script); return promise.then(() => google.maps); } private getScriptSrc(callback: string): string { interface QueryParams { [key: string]: string; }; const query: QueryParams = { v: '3', callback, key: GOOGLE_MAPS_API_KEY, libraries: 'places', }; const params = Object.keys(query).map(key => `${key}=${query[key]}`).join('&'); return `//maps.googleapis.com/maps/api/js?${params}&language=fr`; } } |
As you can see we are first of all initializing the api_key
of the google places autocomplete api and stored it inside the constant variable. Replace it with your own api key. And then we are communicating with the api and extracting the locations details such as city and country. And simply we are returning this data from the service.
Making the HTML5 Template in Angular
Now we will be making the app.component.html
file and copy paste the following code. This will contain the actual data which will be return from the service
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
<h3>Current definition</h3> <dl> <dt>name</dt> <dd> use the <code>name</code>, if different from the computed <code>cityName</code> </dd> <dt>cityName</dt> <dd> <code>locality.long_name</code> if exists, otherwise <code>administrative_area_level_3.short_name</code> </dd> <dt>stateCode</dt> <dd> <code>administrative_area_level_1.short_name</code> if different from <code>administrative_area_level_1.long_name</code>, otherwise not set </dd> <dt>countryName</dt> <dd> <code>country.long_name</code> </dd> <dt>countryCode</dt> <dd> <code>country.short_name</code> </dd> </dl> <h3>Playground</h3> <div> <input placeholder="search for location" autocorrect="off" autocapitalize="off" spellcheck="off" type="text" class="form-control" #search /> </div> <div class="map" #map></div> <div *ngFor="let entry of entries"> <table *ngIf="entry.place" [style.borderColor]="entry.color"> <thead> <tr> <th> <button (click)="remove(entry)">remove</button> </th> <th>short_name</th> <th>types</th> </tr> </thead> <tbody> <tr> <td colspan="2">{{ entry.place.name }}</td> <td>name</td> </tr> <tr *ngFor="let component of entry.place.address_components"> <td>{{ component.long_name }}</td> <td>{{ component.short_name }}</td> <td>{{ component.types }}</td> </tr> <tr> <th colspan="2">⇒ Parsed as</th> <th>field</th> </tr> <tr *ngFor="let locationField of locationFields"> <td colspan="2">{{ entry.location[locationField] }}</td> <td>{{ locationField }}</td> </tr> <!--tr> <td colspan="3"> <pre>{{ entry.place | json }}</pre> </td> </tr--> </tbody> </table> </div> |
As you can see we have the input
field inside the html where we will be taking the user input to get the location and then we have the button to submit the form and then we have the area or section where we will be displaying the map. And we have attached the #map
id to the map. And then we are displaying the location details inside the table.
Now we need to write the source code inside the app.component.ts
file and copy paste the following code. Here we will be defining all the methods and importing the service also. And when the user submits the form by entering the location details then also we will be handling the form in this file as shown below. And also we need to define the geolib.ts
file and copy paste the following code
geolib.ts
1 2 3 4 5 6 7 |
import * as x from 'geolib'; // @types/geolib is written in a way that `import * as geolib from 'geolib';` does not work type geolibType = typeof geolib; const theGeolib = (x as any).default as geolibType; export { theGeolib as geolib }; |
app.component.ts
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
import { Component, Inject, ElementRef, ViewChild, NgZone, } from '@angular/core'; import * as turf from '@turf/turf'; import { ApiService, Maps } from './api.service'; import { geolib } from './geolib'; const colors = [ 'red', 'blue', 'green', 'yellow', 'brown', 'BurlyWood', 'Cyan', 'DarkGreen', 'DarkOrchid', 'DarkOliveGreen', 'Fuchsia', 'GoldenRod', 'Indigo', 'LightCoral', 'MediumSlateBlue', ]; let colorIndex = 0; const place = null as google.maps.places.PlaceResult; type Components = typeof place.address_components; @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { @ViewChild('search') public searchElementRef: ElementRef; @ViewChild('map') public mapElementRef: ElementRef; public entries = []; public place: google.maps.places.PlaceResult; public locationFields = [ 'name', 'cityName', 'stateCode', 'countryName', 'countryCode', ]; private map: google.maps.Map; constructor(apiService: ApiService, private ngZone: NgZone) { apiService.api.then((maps) => { this.initAutocomplete(maps); this.initMap(maps); }); } initAutocomplete(maps: Maps) { let autocomplete = new maps.places.Autocomplete( this.searchElementRef.nativeElement ); autocomplete.addListener('place_changed', () => { this.ngZone.run(() => { this.onPlaceChange(autocomplete.getPlace()); }); }); } initMap(maps: Maps) { this.map = new maps.Map(this.mapElementRef.nativeElement, { zoom: 7, }); this.map.addListener('click', (event) => { const ellipsePoints = toEllipse(this.entries[0].location.bounds); var line = turf.helpers.lineString( ellipsePoints.map((p) => [p.longitude, p.latitude]) ); const pointLatLng = event.latLng as google.maps.LatLng; var point = turf.helpers.point([pointLatLng.lng(), pointLatLng.lat()]); //point = turf.helpers.point([this.entries[0].location.coordinates.longitude, this.entries[0].location.coordinates.latitude]); const isInside = geolib.isPointInside( { latitude: pointLatLng.lat(), longitude: pointLatLng.lng() }, ellipsePoints ); const distance = isInside ? 0 : turf.pointToLineDistance(point, line); console.log('distance', distance * 1000); }); } onPlaceChange(place: google.maps.places.PlaceResult) { this.map.setCenter(place.geometry.location); const color = colors[colorIndex++ % colors.length]; const pin = this.pin(color); const marker = new google.maps.Marker({ position: place.geometry.location, animation: google.maps.Animation.DROP, map: this.map, icon: this.pin(color), }); const rectangle = new google.maps.Rectangle({ strokeColor: color, strokeOpacity: 0.8, strokeWeight: 2, fillColor: color, fillOpacity: 0.35, map: this.map, bounds: place.geometry.viewport, }); const expandedRectangle = new google.maps.Rectangle({ strokeColor: color, strokeOpacity: 0.8, strokeWeight: 0.5, fillColor: color, fillOpacity: 0.2, map: this.map, bounds: expandBounds(place.geometry.viewport.toJSON(), 5000), }); const location = this.locationFromPlace(place); const ellipse = new google.maps.Polygon({ paths: toEllipse(location.bounds).map( ({ latitude, longitude }) => new google.maps.LatLng(latitude, longitude) ), strokeColor: color, strokeOpacity: 1, strokeWeight: 1, fillColor: color, fillOpacity: 0.3, }); ellipse.setMap(this.map); this.entries.unshift({ place, marker, rectangle, expandedRectangle, ellipse, color, location, }); } remove(entry) { entry.marker.setMap(null); entry.rectangle.setMap(null); entry.expandedRectangle.setMap(null); entry.ellipse.setMap(null); this.entries = this.entries.filter((e) => e !== entry); } pin(color) { return { path: 'M 0,0 C -2,-20 -10,-22 -10,-30 A 10,10 0 1,1 10,-30 C 10,-22 2,-20 0,0 z M -2,-30 a 2,2 0 1,1 4,0 2,2 0 1,1 -4,0', fillColor: color, fillOpacity: 1, strokeColor: '#000', strokeWeight: 2, scale: 1, }; } public locationFromPlace(place: google.maps.places.PlaceResult) { const components = place.address_components; if (components === undefined) { return null; } const areaLevel3 = getShort(components, 'administrative_area_level_3'); const locality = getLong(components, 'locality'); const cityName = locality || areaLevel3; const countryName = getLong(components, 'country'); const countryCode = getShort(components, 'country'); const stateCode = getShort(components, 'administrative_area_level_1'); const name = place.name !== cityName ? place.name : null; const coordinates = { latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng(), }; const bounds = place.geometry.viewport.toJSON(); // placeId is in place.place_id, if needed return { name, cityName, countryName, countryCode, stateCode, bounds, coordinates, }; } } function getComponent(components: Components, name: string) { return components.filter((component) => component.types[0] === name)[0]; } function getLong(components: Components, name: string) { const component = getComponent(components, name); return component && component.long_name; } function getShort(components: Components, name: string) { const component = getComponent(components, name); return component && component.short_name; } function toEllipse({ north, south, east, west }: cosmos.LatLngBoundsLiteral) { const latitude = (north + south) / 2; const longitude = (east + west) / 2; const r1 = geolib.getDistance( { latitude: north, longitude }, { latitude: south, longitude } ) / 2; const r2 = geolib.getDistance( { latitude, longitude: west }, { latitude, longitude: east } ) / 2; const center = { latitude, longitude }; const latitudeConv = geolib.getDistance(center, { latitude: latitude + 0.1, longitude }) * 10; const longitudeCong = geolib.getDistance(center, { latitude, longitude: longitude + 0.1 }) * 10; const points: cosmos.Coordinates[] = []; const FULL = Math.PI * 2; for (let i = 0; i <= FULL + 0.0001; i += FULL / 8) { points.push({ latitude: latitude + (r1 * Math.cos(i)) / latitudeConv, longitude: longitude + (r2 * Math.sin(i)) / longitudeCong, }); } return points; } function expandBounds(bounds: cosmos.LatLngBoundsLiteral, meters: number) { const SQRT_2 = 1.4142135623730951; const { longitude: west, latitude: north } = geolib.computeDestinationPoint( { latitude: bounds.north, longitude: bounds.west, }, SQRT_2 * meters, 315 ); const { longitude: east, latitude: south } = geolib.computeDestinationPoint( { latitude: bounds.south, longitude: bounds.east, }, SQRT_2 * meters, 135 ); return { west, north, east, south }; } namespace cosmos { export interface Coordinates { /** * Coordinates latitude. * @type {number} */ latitude: number; /** * Coordinates longitude. * @type {number} */ longitude: number; } export interface LatLngBoundsLiteral { /** * LatLngBoundsLiteral east. * @type {number} */ east: number; /** * LatLngBoundsLiteral north. * @type {number} */ north: number; /** * LatLngBoundsLiteral south. * @type {number} */ south: number; /** * LatLngBoundsLiteral west. * @type {number} */ west: number; } } |
And now if you run the angular
app by running the below command as shown below
ng serve
As you can see we typed the live
location and then it inserted the red
marker inside the map and also displaying the location details inside the table as shown above.