├── favicon.ico ├── .npmignore ├── app ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── tsconfig.app.json ├── styles.css ├── main.ts ├── index.html ├── map-components │ ├── transit-layer.component.ts │ ├── bicycling-layer.component.ts │ ├── traffic-layer.component.ts │ ├── kml-layer.component.ts │ ├── data-layer.component.ts │ ├── map-with-options.component.ts │ ├── street-view-panorama.component.ts │ ├── simple-circle.component.ts │ ├── marker-with-custom-icon.component.ts │ ├── custom-marker.component.ts │ ├── simple-ground-overlay.component.ts │ ├── map-with-streetview.component.ts │ ├── multiple-map.component.ts │ ├── simple-polyline.component.ts │ ├── event-arguments.component.ts │ ├── simple-map.component.ts │ ├── simple-marker.component.ts │ ├── map-change-multiple-properties.component.ts │ ├── places-auto-complete.component.ts │ ├── simple-info-window.component.ts │ ├── polygon.component.ts │ ├── marker-ng-for.component.ts │ ├── experiment.component.ts │ ├── custom-marker-ng-for.component.ts │ ├── drawing-manager.component.ts │ ├── directions-renderer.component.ts │ └── heatmap-layer.component.ts ├── app.component.ts ├── app.module.ts ├── app.html ├── polyfills.ts ├── app.route.ts └── source-code.service.ts ├── .gitignore ├── ng-package.json ├── src ├── services │ ├── config.ts │ ├── navigator-geolocation.ts │ ├── geo-coder.ts │ ├── util.ts │ ├── api-loader.ts │ ├── ngui-map.ts │ └── option-builder.ts ├── directives │ ├── transit-layer.ts │ ├── bicycling-layer.ts │ ├── traffic-layer.ts │ ├── heatmap-layer.ts │ ├── kml-layer.ts │ ├── polyline.ts │ ├── polygon.ts │ ├── drawing-manager.ts │ ├── ground-overlay.ts │ ├── places-auto-complete.ts │ ├── data-layer.ts │ ├── circle.ts │ ├── street-view-panorama.ts │ ├── marker.ts │ ├── directions-renderer.ts │ └── base-map-directive.ts ├── index.ts ├── ngui-map.module.ts └── components │ ├── info-window.ts │ ├── custom-marker.ts │ └── ngui-map.component.ts ├── ISSUE_TEMPLATE.md ├── .travis.yml ├── tsconfig.json ├── tslint.json ├── CHANGELOG.md ├── ngc-pre-compiler.js ├── package.json ├── angular.json ├── webtest.txt └── README.md /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ng2-ui/map/HEAD/favicon.ico -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | app 3 | tsconfig.json 4 | node_modules 5 | npm-debug.log 6 | tmp 7 | -------------------------------------------------------------------------------- /app/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typings 3 | *.log 4 | *.tgz 5 | .idea 6 | tmp 7 | .tmp 8 | .ng_build 9 | dist -------------------------------------------------------------------------------- /ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/ng-packagr/ng-package.schema.json", 3 | "lib": { 4 | "entryFile": "tmp/index.ts" 5 | } 6 | } -------------------------------------------------------------------------------- /src/services/config.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | export const NG_MAP_CONFIG_TOKEN = new InjectionToken('NG_MAP_CONFIG_TOKEN'); 4 | export interface ConfigOption { 5 | apiUrl?: string; 6 | } 7 | -------------------------------------------------------------------------------- /app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015" 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /app/styles.css: -------------------------------------------------------------------------------- 1 | .ls { 2 | width: 30%; 3 | float: right; 4 | } 5 | .ls li { 6 | color: blue; 7 | cursor: pointer; 8 | list-style: none; 9 | } 10 | .components { 11 | width: 65%; 12 | } 13 | .prettyprint { 14 | color: #fff; 15 | font-size: 16px; 16 | } -------------------------------------------------------------------------------- /app/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); -------------------------------------------------------------------------------- /app/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false, 8 | }; -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **IMPORTANT** 2 | _Please be specific with an example. An issue with no example or unclear requirements may be closed._ 3 | 4 | **Steps to reproduce and a minimal demo** 5 | 6 | - _What steps should we try in your demo to see the problem?_ 7 | - _Plunker example_ 8 | - _If you cannot reproduce an issue with a plunker example, it's your environmental issue_ 9 | 10 | **Current behavior** 11 | 12 | - 13 | 14 | **Expected/desired behavior** 15 | 16 | - 17 | 18 | **Other information** 19 | 20 | - 21 | -------------------------------------------------------------------------------- /src/directives/transit-layer.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = []; 7 | const OUTPUTS = [ ]; 8 | 9 | @Directive({ 10 | selector: 'ngui-map > transit-layer', 11 | inputs: INPUTS, 12 | outputs: OUTPUTS, 13 | }) 14 | export class TransitLayer extends BaseMapDirective { 15 | constructor(nguiMapComp: NguiMapComponent) { 16 | super(nguiMapComp, 'TransitLayer', INPUTS, OUTPUTS); 17 | } 18 | } -------------------------------------------------------------------------------- /src/directives/bicycling-layer.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = []; 7 | const OUTPUTS = [ ]; 8 | 9 | @Directive({ 10 | selector: 'ngui-map > bicycling-layer', 11 | inputs: INPUTS, 12 | outputs: OUTPUTS, 13 | }) 14 | export class BicyclingLayer extends BaseMapDirective { 15 | constructor(nguiMapComp: NguiMapComponent) { 16 | super(nguiMapComp, 'BicyclingLayer', INPUTS, OUTPUTS); 17 | } 18 | } -------------------------------------------------------------------------------- /src/directives/traffic-layer.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | import { BaseMapDirective } from './base-map-directive'; 3 | import { NguiMapComponent } from '../components/ngui-map.component'; 4 | 5 | const INPUTS = ['autoRefresh', 'options' ]; 6 | const OUTPUTS = [ ]; 7 | 8 | @Directive({ 9 | selector: 'ngui-map > traffic-layer', 10 | inputs: INPUTS, 11 | outputs: OUTPUTS, 12 | }) 13 | export class TrafficLayer extends BaseMapDirective { 14 | constructor(nguiMapComp: NguiMapComponent) { 15 | super(nguiMapComp, 'TrafficLayer', INPUTS, OUTPUTS); 16 | } 17 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular(2+) Google Maps 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Loading... 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/map-components/transit-layer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Transit Layer

7 | 8 | 9 | 10 | 11 | 12 | 13 |
{{code}}
14 | `}) 15 | export class TransitLayerComponent { 16 | code: string; 17 | constructor(public sc: SourceCodeService) { 18 | sc.getText('TransitLayerComponent').subscribe(text => this.code = text); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/map-components/bicycling-layer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Bicycling Layer

7 | 8 | 9 | 10 | 11 |
{{code}}
` 12 | }) 13 | export class BicyclingLayerComponent { 14 | code: string; 15 | constructor(public sc: SourceCodeService) { 16 | sc.getText('BicyclingLayerComponent').subscribe(text => this.code = text); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/map-components/traffic-layer.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Traffic Layer

7 | 8 | 9 | 10 | 11 | 12 | 13 |
{{code}}
14 | `}) 15 | export class TrafficLayerComponent { 16 | code: string; 17 | constructor(public sc: SourceCodeService) { 18 | sc.getText('TrafficLayerComponent').subscribe(text => this.code = text); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/directives/heatmap-layer.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 'data', 'dissipating', 'gradient', 'maxIntensity', 'opacity', 'radius', 'options' ]; 7 | const OUTPUTS = []; 8 | 9 | @Directive({ 10 | selector: 'ngui-map > heatmap-layer', 11 | inputs: INPUTS, 12 | outputs: OUTPUTS, 13 | }) 14 | export class HeatmapLayer extends BaseMapDirective { 15 | public libraryName = 'visualization'; 16 | 17 | constructor(nguiMapComp: NguiMapComponent) { 18 | super(nguiMapComp, 'HeatmapLayer', INPUTS, OUTPUTS); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/directives/kml-layer.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 'clickable', 'preserveViewport', 'screenOverlays', 'suppressInfoWindows', 'url', 'zIndex', 'options' ]; 7 | const OUTPUTS = [ 'click', 'defaultviewport_changed', 'status_changed' ]; 8 | 9 | @Directive({ 10 | selector: 'ngui-map > kml-layer', 11 | inputs: INPUTS, 12 | outputs: OUTPUTS, 13 | }) 14 | export class KmlLayer extends BaseMapDirective { 15 | constructor(nguiMapComp: NguiMapComponent) { 16 | super(nguiMapComp, 'KmlLayer', INPUTS, OUTPUTS); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/map-components/kml-layer.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Kml Layer

7 | 8 | 9 | 10 | 11 | 12 | 13 |
{{code}}
14 | `}) 15 | export class KmlLayerComponent { 16 | code: string; 17 | constructor(public sc: SourceCodeService) { 18 | sc.getText('KmlLayerComponent').subscribe(text => this.code = text); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/map-components/data-layer.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Data Layer

7 | 8 | 9 | 10 | 11 | 12 | 13 |
{{code}}
14 | `}) 15 | export class DataLayerComponent { 16 | code: string; 17 | constructor(public sc: SourceCodeService) { 18 | sc.getText('DataLayerComponent').subscribe(text => this.code = text); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/app.component.ts: -------------------------------------------------------------------------------- 1 | declare var PR: any; 2 | 3 | import { Component} from '@angular/core'; 4 | import { Router, NavigationEnd } from '@angular/router'; 5 | 6 | @Component({ 7 | selector: 'ngui-map-app', 8 | templateUrl: './app.html', 9 | }) 10 | export class AppComponent { 11 | public center = 'Brampton, Canada'; 12 | public positions = [ ]; 13 | constructor(router: Router) { 14 | router.events.subscribe( event => { 15 | // TODO: bad idea to deal with document directly 16 | if (document.querySelector('.prettyprinted')) { 17 | document.querySelector('.prettyprinted').classList.remove('prettyprinted'); 18 | } 19 | if (event instanceof NavigationEnd) { // Start, Cancel, Error 20 | setTimeout(e => PR.prettyPrint(), 500); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/map-components/map-with-options.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Map With Options - satellite view

7 | 8 | 9 | 10 | 11 |
{{code}}
12 | `}) 13 | export class MapWithOptionsComponent { 14 | code: string; 15 | 16 | allOptions = { 17 | center: {lat: 36.964, lng: -122.015}, 18 | zoom: 18, 19 | mapTypeId: 'satellite', 20 | tilt: 45 21 | }; 22 | 23 | constructor(public sc: SourceCodeService) { 24 | sc.getText('MapWithOptionsComponent').subscribe(text => this.code = text); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/map-components/street-view-panorama.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple StreetView

7 | 8 | 13 | 14 | 15 | 16 | 17 |
{{code}}
18 | `}) 19 | export class StreetViewPanoramaComponent { 20 | code: string; 21 | 22 | constructor(public sc: SourceCodeService) { 23 | sc.getText('StreetViewPanoramaComponent').subscribe(text => this.code = text); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/directives/polyline.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 7 | 'clickable', 'draggable', 'editable', 'geodesic', 'icons', 'path', 'strokeColor', 8 | 'strokeOpacity', 'strokeWeight', 'visible', 'zIndex', 'options' 9 | ]; 10 | const OUTPUTS = [ 11 | 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'mousedown', 12 | 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick' 13 | ]; 14 | 15 | @Directive({ 16 | selector: 'ngui-map > polyline', 17 | inputs: INPUTS, 18 | outputs: OUTPUTS, 19 | }) 20 | export class Polyline extends BaseMapDirective { 21 | constructor(nguiMapComp: NguiMapComponent) { 22 | super(nguiMapComp, 'Polyline', INPUTS, OUTPUTS); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/map-components/simple-circle.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple Circle

7 | 8 | 15 | 16 | 17 | 18 | 19 |
{{code}}
` 20 | }) 21 | export class SimpleCircleComponent { 22 | code: string; 23 | constructor(public sc: SourceCodeService) { 24 | sc.getText('SimpleCircleComponent').subscribe(text => this.code = text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/map-components/marker-with-custom-icon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Marker With Custom Icon

7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 |
{{code}}
21 | `}) 22 | export class MarkerWithCustomIconComponent { 23 | code: string; 24 | constructor(public sc: SourceCodeService) { 25 | sc.getText('MarkerWithCustomIconComponent').subscribe(text => this.code = text); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/directives/polygon.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 7 | 'clickable', 'draggable', 'editable', 'fillColor', 'fillOpacity', 'geodesic', 'paths', 8 | 'strokeColor', 'strokeOpacity', 'strokePosition', 'strokeWeight', 'visible', 'zIndex', 'options', 9 | ]; 10 | const OUTPUTS = [ 11 | 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'mousedown', 12 | 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick', 13 | ]; 14 | 15 | @Directive({ 16 | selector: 'ngui-map>polygon, ngui-map>map-polygon', 17 | inputs: INPUTS, 18 | outputs: OUTPUTS, 19 | }) 20 | export class Polygon extends BaseMapDirective { 21 | constructor(nguiMapComp: NguiMapComponent) { 22 | super(nguiMapComp, 'Polygon', INPUTS, OUTPUTS); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/directives/drawing-manager.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 7 | 'options', 8 | 'circleOptions', 'drawingControl', 'drawingControlOptions', 'drawingMode', 9 | 'map', 'markerOptions', 'polygonOptions', 'polylineOptions', 'rectangleOptions' 10 | ]; 11 | const OUTPUTS = [ 12 | 'circlecomplete', 'markercomplete', 'overlaycomplete', 13 | 'polygoncomplete', 'polylinecomplete', 'rectanglecomplete' 14 | ]; 15 | 16 | @Directive({ 17 | selector: 'ngui-map > drawing-manager', 18 | inputs: INPUTS, 19 | outputs: OUTPUTS, 20 | }) 21 | export class DrawingManager extends BaseMapDirective { 22 | constructor(nguiMapComp: NguiMapComponent) { 23 | super(nguiMapComp, 'DrawingManager', INPUTS, OUTPUTS); 24 | this.libraryName = 'drawing'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/map-components/custom-marker.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Custom Marker

7 | 8 | 9 |
Hi, USA 10 | 11 |
12 |
13 | 14 |
15 | 16 | 17 | 18 |
{{code}}
19 | `}) 20 | 21 | export class CustomMarkerComponent { 22 | code: string; 23 | constructor(public sc: SourceCodeService) { 24 | sc.getText('CustomMarkerComponent').subscribe(text => this.code = text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/map-components/simple-ground-overlay.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple Ground Overlay

7 | 8 | 13 | 14 | 15 | 16 | 17 |
{{code}}
18 | `}) 19 | export class SimpleGroundOverlayComponent { 20 | bounds = { north: 40.773941, south: 40.712216, east: -74.12544, west: -74.22655 }; 21 | code: string; 22 | 23 | constructor(public sc: SourceCodeService) { 24 | sc.getText('SimpleGroundOverlayComponent').subscribe(text => this.code = text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/services/navigator-geolocation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, Observer } from 'rxjs'; 3 | import { IJson } from './util'; 4 | 5 | /** 6 | * service for navigator.geolocation methods 7 | */ 8 | @Injectable() 9 | export class NavigatorGeolocation { 10 | 11 | getCurrentPosition(geoLocationOptions?: IJson): Observable { 12 | geoLocationOptions = geoLocationOptions || { timeout: 5000 }; 13 | 14 | return new Observable((responseObserver: Observer) => { 15 | if (navigator.geolocation) { 16 | navigator.geolocation.getCurrentPosition( 17 | (position) => { 18 | responseObserver.next(position); 19 | responseObserver.complete(); 20 | }, 21 | (evt) => responseObserver.error(evt), 22 | geoLocationOptions 23 | ); 24 | } else { 25 | responseObserver.error('Browser Geolocation service failed.'); 26 | } 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/map-components/map-with-streetview.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Map With StreetView

7 | 13 | 14 |
15 | 16 | 17 | 18 |
{{code}}
19 | `, 20 | styles: [`#sv {width: 50%; height: 300px} ngui-map {width: 50%; float: left}`] 21 | }) 22 | export class MapWithStreetviewComponent { 23 | code: string; 24 | 25 | constructor(public sc: SourceCodeService) { 26 | sc.getText('MapWithStreetviewComponent').subscribe(text => this.code = text); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/map-components/multiple-map.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple Map 1

7 | 8 | 9 | 10 | 11 |

Simple Map 2

12 | 13 | 14 |

Simple Map 3

15 | 16 | 17 | 18 | 19 | 20 | 21 |
{{code}}
22 | `}) 23 | export class MultipleMapComponent { 24 | positions = []; 25 | code: string; 26 | constructor(public sc: SourceCodeService) { 27 | sc.getText('MultipleMapComponent').subscribe(text => this.code = text); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | # To start firefox, xvfb(X virtual framebuffer) 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - sleep 5 9 | 10 | after_success: 11 | - test $TRAVIS_PULL_REQUEST == "false" && test $TRAVIS_BRANCH == "master" && npm run gh-pages 12 | 13 | env: 14 | global: 15 | secure: rERtYBPaeGIlU96ATx7Omp0vHjCZMbwTgFGqEA2KY0MQwBSTZ9buW1zpkSATr/Qgtv+/1xcWl2MAT9fg7LzRp3WAnTVkyV0zJwVhjn9METTgDuRoC7d8ktE9W4Munq6w4dOb5ai/shdxQnoEcRy+sE+3Lr3FyEw9C7FTjlZJhD7a5+A5UGOfqD0rhFyoSwgqGeKEa6LQjEZVs5DwKF5JIorTXN8so0AiBJFQI3wDUHLqZs4wyk6eA/y/q+G2OZos+To2E3qlvinhAM8DPwito9Kf4Q3Xw4ncli02M4B7/iG8b1YYr6POL30qaPCZQ7z9RlFV72BTtd99q9wmBYd0MsXgToGASbfpA+VSLYtcfQMb/gknF2WVwpjLb+tD8bJCP4cFeKjjOouQ0sTe9/w8nHgdEarDyUEUF2dR42OAUpbKOZtmARsvSsAYI/Zt+XZDlXkRBgq70WgXPFlg8xxeYopGRURz18fNoO6qm+/y6HJWv+btmgcsVu+RYIzTz4yWHxp+a9WjUwJiUwUvEI5M0dIOjjRSV9POaSlN423oxyH6krXmsTT1MOCZiiqLl/ilcduM05WYT4PxpzPH2imla7I+zNUhPxs6PhxloSV/lFejyxfAoPMn5dIjC7PrEoPivnRUjplWLhB+J7/u9s0j+64WA0J/9cHavxSQCOHI31M= -------------------------------------------------------------------------------- /app/map-components/simple-polyline.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple Polyine

7 | 8 | 14 | 15 | 16 | 17 | 18 |
{{code}}
19 | `}) 20 | export class SimplePolylineComponent { 21 | path = [ 22 | {lat: 37.772, lng: -122.214}, 23 | {lat: 21.291, lng: -157.821}, 24 | {lat: -18.142, lng: 178.431}, 25 | {lat: -27.467, lng: 153.027} 26 | ]; 27 | code: string; 28 | 29 | constructor(public sc: SourceCodeService) { 30 | sc.getText('SimplePolylineComponent').subscribe(text => this.code = text); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "sourceMap": true, 9 | "strictNullChecks": false, 10 | "importHelpers": true, 11 | "lib": ["dom", "es2016"], 12 | "baseUrl": "src", 13 | "paths": { 14 | "@ngui/map": ["./index"], 15 | // workaround for https://github.com/ng2-ui/utils/issues/5 16 | "@ngui/utils": ["../node_modules/@ngui/utils/src/index.ts"] 17 | }, 18 | "typeRoots": [ 19 | "./node_modules/@types" 20 | ] 21 | }, 22 | "include": [ 23 | "node_modules/@ngui/utils/src/**/*.ts", 24 | "src/**/*.ts", 25 | "app/**/*.ts" 26 | ], 27 | "exclude": [ 28 | "node_modules", 29 | "app/build" 30 | ], 31 | "compileOnSave": false, 32 | "buildOnSave": false, 33 | "angularCompilerOptions": { 34 | "strictMetadataEmit": true, 35 | "skipTemplateCodegen": true, 36 | "preserveWhitespaces": false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/map-components/event-arguments.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Event Arguments

7 | Implementation of https://developers.google.com/maps/documentation/javascript/examples/event-arguments 8 |

9 | Click the map to add marker and center it. 10 | 11 | 12 | 13 | 14 | 15 | 16 |
{{code}}
17 | `}) 18 | export class EventArgumentsComponent { 19 | positions: any[] = []; 20 | code: string; 21 | 22 | constructor(public sc: SourceCodeService) { 23 | sc.getText('EventArgumentsComponent').subscribe(text => this.code = text); 24 | } 25 | 26 | onClick(event) { 27 | if (event instanceof MouseEvent) return; 28 | this.positions.push(event.latLng); 29 | event.target.panTo(event.latLng); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/map-components/simple-map.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple Map

7 | 11 | "center" can be an; 12 |
    13 |
  • lat/lng array e.g., [42.99, -77.79] 14 |
  • an address. e.g. Brampton, Canada 15 |
  • or, none(for the current position) 16 |
17 | 19 | 20 | 21 | 22 |
{{code}}
` 23 | }) 24 | export class SimpleMapComponent { 25 | code: string; 26 | constructor(public sc: SourceCodeService) { 27 | sc.getText('SimpleMapComponent').subscribe(text => this.code = text); 28 | } 29 | onClick(event) { 30 | if (event instanceof MouseEvent) { 31 | return false; 32 | } 33 | console.log('map is clicked', event, event.target); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/services/geo-coder.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnDestroy } from '@angular/core'; 2 | import { Observable, Observer } from 'rxjs'; 3 | import { NgMapApiLoader } from './api-loader'; 4 | 5 | /** 6 | * Provides [defered/promise API](https://docs.angularjs.org/api/ng/service/$q) 7 | * service for Google Geocoder service 8 | */ 9 | 10 | @Injectable() 11 | export class GeoCoder implements OnDestroy { 12 | private apiLoaderSubs = []; 13 | constructor(private apiLoader: NgMapApiLoader) {} 14 | 15 | geocode(options: google.maps.GeocoderRequest) { 16 | return new Observable((responseObserver: Observer) => { 17 | this.apiLoaderSubs.push(this.apiLoader.api$ 18 | .subscribe(() => this.requestGeocode(options, responseObserver))); 19 | }); 20 | } 21 | 22 | ngOnDestroy() { 23 | this.apiLoaderSubs.map(sub => sub.unsubscribe()); 24 | } 25 | 26 | private requestGeocode(options, observer) { 27 | const geocoder = new google.maps.Geocoder(); 28 | geocoder.geocode(options, function (results, status) { 29 | if (status === google.maps.GeocoderStatus.OK) { 30 | observer.next(results); 31 | observer.complete(); 32 | } else { 33 | observer.error(results); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/map-components/simple-marker.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple Marker

7 | 13 | 18 | 19 | 20 | 21 | 22 |
{{code}}
` 23 | }) 24 | export class SimpleMarkerComponent { 25 | code: string; 26 | constructor(public sc: SourceCodeService) { 27 | sc.getText('SimpleMarkerComponent').subscribe(text => this.code = text); 28 | } 29 | log(event, str) { 30 | if (event instanceof MouseEvent) { 31 | return false; 32 | } 33 | console.log('event .... >', event, str); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/map-components/map-change-multiple-properties.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Map Change Multiple Properties

7 | 12 |
center: {{mapInfo.center}}, zoom: {{mapInfo.zoom}}
13 | 17 | 18 | 19 | 20 |
{{code}}
21 | `}) 22 | export class MapChangeMultiplePropertiesComponent { 23 | mapProps: any = { 24 | center: 'some-invalid-location', 25 | zoom: 11 26 | }; 27 | mapInfo: any = {}; 28 | code: string; 29 | 30 | constructor(public sc: SourceCodeService) { 31 | sc.getText('MapChangeMultiplePropertiesComponent').subscribe(text => this.code = text); 32 | } 33 | 34 | onIdle(event) { 35 | let map = event.target; 36 | this.mapInfo.center = [map.getCenter().lat(), map.getCenter().lng()]; 37 | this.mapInfo.zoom = map.getZoom(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { LocationStrategy, HashLocationStrategy } from '@angular/common'; 6 | import { NguiUtilsModule } from '@ngui/utils'; 7 | import { NguiMapModule } from '@ngui/map'; 8 | import { SourceCodeService } from './source-code.service'; 9 | import { HttpClientModule } from '@angular/common/http'; 10 | 11 | import { AppComponent } from './app.component'; 12 | // import { Codeblock } from 'ng2-prism/codeblock'; 13 | 14 | import { APP_ROUTER_PROVIDERS, APP_ROUTER_COMPONENTS } from './app.route'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | BrowserModule, 19 | FormsModule, 20 | HttpClientModule, 21 | APP_ROUTER_PROVIDERS, 22 | // NguiMapModule, 23 | NguiMapModule.forRoot({ 24 | apiUrl: 'https://maps.google.com/maps/api/js?key=AIzaSyCbMGRUwcqKjlYX4h4-P6t-xcDryRYLmCM' + 25 | '&libraries=visualization,places,drawing', 26 | }), 27 | NguiUtilsModule 28 | ], 29 | declarations: [AppComponent, APP_ROUTER_COMPONENTS], 30 | providers: [ 31 | SourceCodeService, 32 | { provide: LocationStrategy, useClass: HashLocationStrategy }, 33 | ], 34 | bootstrap: [AppComponent], 35 | }) 36 | export class AppModule {} -------------------------------------------------------------------------------- /app/map-components/places-auto-complete.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, ChangeDetectorRef} from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Place Autocomplete Address Form

7 | 10 |

11 | 12 | place: {{address | json}} 13 |

14 | 15 | 16 | 17 |
{{code}}
18 | ` }) 19 | export class PlacesAutoCompleteComponent { 20 | autocomplete: any; 21 | address: any = {}; 22 | center: any; 23 | code: string; 24 | 25 | constructor(private ref: ChangeDetectorRef, public sc: SourceCodeService) { 26 | sc.getText('PlacesAutoCompleteComponent').subscribe(text => this.code = text); 27 | } 28 | 29 | initialized(autocomplete: any) { 30 | this.autocomplete = autocomplete; 31 | } 32 | placeChanged(place) { 33 | this.center = place.geometry.location; 34 | for (let i = 0; i < place.address_components.length; i++) { 35 | let addressType = place.address_components[i].types[0]; 36 | this.address[addressType] = place.address_components[i].long_name; 37 | } 38 | this.ref.detectChanges(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/map-components/simple-info-window.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Simple InfoWindow

7 | 8 | 9 | 10 |
11 | lat: {{ marker.lat }}, lng: {{ marker.lng }} 12 |
13 | 14 |
15 |
16 | Please click the marker to see a info window 17 | 18 | 19 | 20 |
{{code}}
21 | `}) 22 | export class SimpleInfoWindowComponent { 23 | marker = { 24 | display: true, 25 | lat: null, 26 | lng: null, 27 | }; 28 | code: string; 29 | 30 | constructor(public sc: SourceCodeService) { 31 | sc.getText('SimpleInfoWindowComponent').subscribe(text => this.code = text); 32 | } 33 | 34 | clicked({target: marker}) { 35 | this.marker.lat = marker.getPosition().lat(); 36 | this.marker.lng = marker.getPosition().lng(); 37 | 38 | marker.nguiMapComponent.openInfoWindow('iw', marker); 39 | } 40 | 41 | hideMarkerInfo() { 42 | this.marker.display = !this.marker.display; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/map-components/polygon.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SourceCodeService } from '../source-code.service'; 3 | 4 | @Component({ 5 | template: ` 6 |

Polygon

7 | 8 | 15 | 16 | 17 | 24 | 25 | 26 | 27 | 28 |
{{code}}
29 | `}) 30 | export class PolygonComponent { 31 | paths = [[ 32 | {lat: 25.774, lng: -80.190}, 33 | {lat: 18.466, lng: -66.118}, 34 | {lat: 32.321, lng: -64.757} 35 | ], [ 36 | {lat: 28.745, lng: -70.579}, 37 | {lat: 29.570, lng: -67.514}, 38 | {lat: 27.339, lng: -66.668} 39 | ]]; 40 | 41 | code: string; 42 | constructor(public sc: SourceCodeService) { 43 | sc.getText('PolygonComponent').subscribe(text => this.code = text); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { BicyclingLayer } from './directives/bicycling-layer'; 2 | export { NavigatorGeolocation } from './services/navigator-geolocation'; 3 | export { OptionBuilder } from './services/option-builder'; 4 | export { NG_MAP_CONFIG_TOKEN, ConfigOption } from './services/config'; 5 | export { NgMapApiLoader, NgMapAsyncApiLoader, NgMapAsyncCallbackApiLoader } from './services/api-loader'; 6 | 7 | export { NguiMapComponent } from './components/ngui-map.component'; 8 | export { InfoWindow } from './components/info-window'; 9 | export { CustomMarker } from './components/custom-marker'; 10 | 11 | export { Circle } from './directives/circle'; 12 | export { DataLayer } from './directives/data-layer'; 13 | export { DirectionsRenderer } from './directives/directions-renderer'; 14 | export { DrawingManager } from './directives/drawing-manager'; 15 | export { GeoCoder } from './services/geo-coder'; 16 | export { GroundOverlay } from './directives/ground-overlay'; 17 | export { HeatmapLayer } from './directives/heatmap-layer'; 18 | export { KmlLayer } from './directives/kml-layer'; 19 | export { Marker } from './directives/marker'; 20 | export { NguiMap } from './services/ngui-map'; 21 | export { PlacesAutoComplete } from './directives/places-auto-complete'; 22 | export { Polygon } from './directives/polygon'; 23 | export { Polyline } from './directives/polyline'; 24 | export { StreetViewPanorama } from './directives/street-view-panorama'; 25 | export { TrafficLayer } from './directives/traffic-layer'; 26 | export { TransitLayer } from './directives/transit-layer'; 27 | 28 | export { NguiMapModule } from './ngui-map.module'; 29 | -------------------------------------------------------------------------------- /app/map-components/marker-ng-for.component.ts: -------------------------------------------------------------------------------- 1 | import { of } from 'rxjs'; 2 | import { Component } from '@angular/core'; 3 | import { SourceCodeService } from '../source-code.service'; 4 | 5 | 6 | @Component({ 7 | template: ` 8 |

Marker Wigh *ngFor

9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 |
{{code}}
18 | `}) 19 | export class MarkerNgForComponent { 20 | public positions = []; 21 | 22 | code: string; 23 | 24 | constructor(public sc: SourceCodeService) { 25 | this.positions = this.getRandomMarkers(); 26 | sc.getText('MarkerNgForComponent').subscribe(text => this.code = text); 27 | } 28 | 29 | getRandomMarkers() { 30 | let randomLat: number, randomLng: number; 31 | 32 | let positions = []; 33 | for (let i = 0 ; i < 9; i++) { 34 | randomLat = Math.random() * (43.7399 - 43.7300) + 43.7300; 35 | randomLng = Math.random() * (-79.7600 - -79.7699) + -79.7699; 36 | positions.push([randomLat, randomLng]); 37 | } 38 | return positions; 39 | } 40 | 41 | showMarkersFromObservable() { 42 | of(this.getRandomMarkers()) // Think this as http call 43 | .subscribe( positions => { 44 | this.positions = positions; 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/directives/ground-overlay.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 'url', 'bounds', 'clickable', 'opacity' ]; 7 | const OUTPUTS = [ 'click', 'dblclick' ]; 8 | 9 | @Directive({ 10 | selector: 'ngui-map > ground-overlay', 11 | inputs: INPUTS, 12 | outputs: OUTPUTS, 13 | }) 14 | export class GroundOverlay extends BaseMapDirective { 15 | public mapObject: google.maps.GroundOverlay; 16 | public objectOptions: google.maps.GroundOverlayOptions = {}; 17 | 18 | constructor(nguiMapComp: NguiMapComponent) { 19 | super(nguiMapComp, 'GroundOverlay', INPUTS, OUTPUTS); 20 | } 21 | 22 | // re-declaring initialize function. called when map is ready 23 | initialize(): void { 24 | // url, bounds are not the options of GroundOverlay 25 | this.objectOptions = this.optionBuilder.googlizeAllInputs(['clickable', 'opacity'], this); 26 | console.log(this.mapObjectName, 'initialization objectOptions', this.objectOptions); 27 | 28 | // noinspection TypeScriptUnresolvedFunction 29 | this.mapObject = new google.maps.GroundOverlay(this['url'], this['bounds'], this.objectOptions); 30 | this.mapObject.setMap(this.nguiMapComponent.map); 31 | this.mapObject['mapObjectName'] = this.mapObjectName; 32 | 33 | // set google events listeners and emits to this outputs listeners 34 | this.nguiMap.setObjectEvents(this.outputs, this, 'mapObject'); 35 | 36 | this.nguiMapComponent.addToMapObjectGroup(this.mapObjectName, this.mapObject); 37 | this.initialized$.emit(this.mapObject); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Examples

    3 |
  • Simple Map
  • 4 |
  • Simple Circle
  • 5 |
  • Simple Marker
  • 6 |
  • Marker With *ngFor
  • 7 |
  • Marker With Custom Icon
  • 8 |
  • Simple InfoWindow
  • 9 |
  • Multiple Map
  • 10 |
  • Polygon
  • 11 |
  • Map With Options
  • 12 |
  • Map With Streetview
  • 13 |
  • Change Multiple Properties Of Map
  • 14 |
  • Simple Polyline
  • 15 |
  • Simple Ground Overlay
  • 16 |
  • Bicycling Layer
  • 17 |
  • Traffic Layer
  • 18 |
  • Transit Layer
  • 19 |
  • Heatmap Layer
  • 20 |
  • KML Layer
  • 21 |
  • Data Layer
  • 22 |
  • Street View Panorama
  • 23 |
  • Places Auto Complete
  • 24 |
  • Directions Renderer
  • 25 |
  • Drawing Manager
  • 26 |
  • Event Arguments
  • 27 |
  • Custom Marker
  • 28 |
  • Custom Marker *ngFor
  • 29 |
  • Experiment
  • 30 |
31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /src/directives/places-auto-complete.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Input, 3 | Output, 4 | Directive, 5 | EventEmitter, 6 | ElementRef, 7 | } from '@angular/core'; 8 | 9 | import { NgMapApiLoader } from '../services/api-loader'; 10 | import { OptionBuilder } from '../services/option-builder'; 11 | import { missingLibraryError } from '../services/util'; 12 | import { first } from 'rxjs/operators'; 13 | 14 | @Directive({ 15 | selector: '[places-auto-complete]' 16 | }) 17 | export class PlacesAutoComplete { 18 | @Input() bounds: any; 19 | @Input() componentRestrictions: any; 20 | @Input() types: string[]; 21 | 22 | @Output('place_changed') place_changed: EventEmitter = new EventEmitter(); 23 | @Output() initialized$: EventEmitter = new EventEmitter(); 24 | 25 | public objectOptions: any; 26 | public autocomplete: google.maps.places.Autocomplete; 27 | 28 | constructor( 29 | public optionBuilder: OptionBuilder, 30 | public elementRef: ElementRef, 31 | public apiLoader: NgMapApiLoader, 32 | ) { 33 | apiLoader.load(); 34 | apiLoader.api$ 35 | .pipe(first()) 36 | .subscribe(() => this.initialize()); 37 | } 38 | 39 | // only called when map is ready 40 | initialize = (): void => { 41 | this.objectOptions = 42 | this.optionBuilder.googlizeAllInputs(['bounds', 'componentRestrictions', 'types'], this); 43 | console.log('places autocomplete options', this.objectOptions); 44 | 45 | if (!google.maps.places) { 46 | throw missingLibraryError('PlacesAutoComplete', 'places'); 47 | } 48 | 49 | this.autocomplete = new google.maps.places.Autocomplete( 50 | this.elementRef.nativeElement, 51 | this.objectOptions 52 | ); 53 | console.log('this.autocomplete', this.autocomplete); 54 | 55 | this.autocomplete.addListener('place_changed', place => { 56 | this.place_changed.emit(this.autocomplete.getPlace()); 57 | }); 58 | 59 | this.initialized$.emit(this.autocomplete); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/directives/data-layer.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = ['controlPosition', 'controls', 'drawingMode', 'featureFactory', 'style', 'geoJson', 'geoJsonUrl']; 7 | const OUTPUTS = [ 8 | 'addfeature', 'click', 'dblclick', 'mousedown', 'mouseout', 'mouseover', 9 | 'mouseup', 'removefeature', 'removeproperty', 'rightclick', 'setgeometry', 'setproperty' 10 | ]; 11 | 12 | @Directive({ 13 | selector: 'ngui-map > data-layer', 14 | inputs: INPUTS, 15 | outputs: OUTPUTS, 16 | }) 17 | export class DataLayer extends BaseMapDirective { 18 | constructor(nguiMapComponent: NguiMapComponent) { 19 | super(nguiMapComponent, 'Data', INPUTS, OUTPUTS); 20 | } 21 | 22 | // only called when map is ready 23 | initialize(): void { 24 | if (this['geoJson']) { 25 | // addGeoJson from an object 26 | console.log('this.geoJson', this['geoJson']); 27 | this.nguiMapComponent.map.data.addGeoJson(this['geoJson']); 28 | } else if (this['geoJsonUrl']) { 29 | // loadGeoJson from a URL 30 | console.log('this.geoJsonUrl', this['geoJsonUrl']); 31 | this.nguiMapComponent.map.data.loadGeoJson(this['geoJsonUrl']); 32 | } 33 | else { 34 | this.objectOptions = this.optionBuilder.googlizeAllInputs(this.inputs, this); 35 | console.log(this.mapObjectName, 'initialization objectOptions', this.objectOptions); 36 | this.nguiMapComponent.map.data.add(this.objectOptions); 37 | } 38 | 39 | // unlike others, data belongs to map. e.g., map.data.loadGeoJson(), map.data.add() 40 | this.mapObject = this.nguiMapComponent.map.data; 41 | 42 | // set google events listeners and emits to this outputs listeners 43 | this.nguiMap.setObjectEvents(this.outputs, this, 'mapObject'); 44 | 45 | this.nguiMapComponent.addToMapObjectGroup(this.mapObjectName, this.mapObject); 46 | this.initialized$.emit(this.mapObject); 47 | } 48 | } -------------------------------------------------------------------------------- /app/map-components/experiment.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectorRef } from '@angular/core'; 2 | declare let google: any; 3 | import { SourceCodeService } from '../source-code.service'; 4 | 5 | @Component({ 6 | template: ` 7 |

{{pos}}

8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 |
{{code}}
17 | `}) 18 | export class ExperimentComponent { 19 | positions = []; 20 | heatmap: any; 21 | code: string; 22 | 23 | constructor(private cdr: ChangeDetectorRef, public sc: SourceCodeService) { 24 | sc.getText('ExperimentComponent').subscribe(text => this.code = text); 25 | } 26 | 27 | onHeatmapInitialized = (evt) => { 28 | this.heatmap = evt; 29 | 30 | let randomLat = Math.random() * 0.0099 + 43.7250; 31 | let randomLon = Math.random() * 0.0099 + -79.7699; 32 | 33 | let values = []; 34 | values.push(new google.maps.LatLng(randomLat, randomLon)); 35 | values.push(new google.maps.LatLng(randomLat, randomLon)); 36 | values.push(new google.maps.LatLng(randomLat, randomLon)); 37 | values.push(new google.maps.LatLng(randomLat, randomLon)); 38 | values.push(new google.maps.LatLng(randomLat, randomLon)); 39 | values.push(new google.maps.LatLng(randomLat, randomLon)); 40 | values.push(new google.maps.LatLng(randomLat, randomLon)); 41 | values.push(new google.maps.LatLng(randomLat, randomLon)); 42 | values.push(new google.maps.LatLng(randomLat, randomLon)); 43 | this.heatmap.setData(values); 44 | 45 | this.positions.push([43.72723792568628, -79.7657115210506]); 46 | this.positions.push([randomLat, randomLon]); 47 | console.log(this.positions); 48 | this.cdr.detectChanges(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "directive-selector": [false, "attribute", ["ngui-map"], "camelCase"], 5 | "component-selector": [true, "element", ["ngui-map", "custom", "info"], "kebab-case"], 6 | "use-input-property-decorator": false, 7 | "use-output-property-decorator": false, 8 | "use-host-property-decorator": true, 9 | "no-attribute-parameter-decorator": true, 10 | "no-input-rename": true, 11 | "no-output-rename": true, 12 | "no-forward-ref" : true, 13 | "use-life-cycle-interface": true, 14 | "use-pipe-transform-interface": true, 15 | "pipe-naming": [false, "camelCase", "ngui-map"], 16 | "import-spacing": true, 17 | "class-name": true, 18 | "comment-format": [true, "check-space"], 19 | "indent": [true, "spaces"], 20 | "member-access": false, 21 | "member-ordering": [ 22 | true, 23 | "public-before-private", 24 | "static-before-instance", 25 | "variables-before-functions" 26 | ], 27 | "no-unused-variable": true, 28 | "no-duplicate-variable": true, 29 | "no-eval": true, 30 | "no-internal-module": true, 31 | "no-trailing-whitespace": true, 32 | "trailing-comma": [false], 33 | "no-var-keyword": true, 34 | "one-line": [ 35 | true, 36 | "check-open-brace", 37 | "check-whitespace" 38 | ], 39 | "quotemark": [true, "single"], 40 | "semicolon": [true, "always"], 41 | "triple-equals": [true, "allow-null-check"], 42 | "typedef-whitespace": [ 43 | true, 44 | { 45 | "call-signature": "nospace", 46 | "index-signature": "nospace", 47 | "parameter": "nospace", 48 | "property-declaration": "nospace", 49 | "variable-declaration": "nospace" 50 | } 51 | ], 52 | "variable-name": [true, "ban-keywords"], 53 | "whitespace": [ 54 | true, 55 | "check-branch", 56 | "check-decl", 57 | "check-operator", 58 | "check-separator", 59 | "check-type" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.17.0 (2017-03-29) 2 | 3 | * renamed module to @ngui/map 4 | 5 | # 0.16.0 (2017-03-04) 6 | 7 | * Added @Output initialized$ on all dierctives 8 | * Fixed emit/subscribe issue, issue #99 9 | 10 | # 0.15.0 (2017-01-23) 11 | 12 | * added geo fallback options for map and marker 13 | * **0.15.1 (2017-01-29)** 14 | * Added more webtest 15 | * **0.15.2 (2017-02-11)** 16 | * Fixed a bug of geocode callback losing setMethod name 17 | * Exposed `mapReady$` and `initialized$` as an Output 18 | * **0.15.3 (2017-02-13)** 19 | * Made available for both imports of `NguiMapModule` and `NguiMapModule.forRoot()` 20 | * **0.15.4 (2017-02-16)** 21 | * Upgraded webpack from 1.* tp 2.* 22 | * Fixed multiple map instance not loading on the first page. 23 | * **0.15.5 (2017-02-16)** Removed debug line 24 | * **0.15.6/7 (2017-02-16)** 25 | * Fixed custom marker positioning 26 | * issue #91, #92 fixed bug of not displaying map with places-autocomplete 27 | 28 | # 0.14.0 (2017-01-12) 29 | 30 | * new directive, custom-marker 31 | 32 | # 0.13.2 (2016-12-20) 33 | 34 | * event parameter is changed from object to event. event.target is a google map object from now on. 35 | 36 | # 0.13.0 (2016-12-23) 37 | 38 | * new directive, drawing-manager 39 | 40 | # 0.12.0 (2016-12-9) 41 | 42 | * new directive, directions-renderer 43 | 44 | # 0.11.0 (2016-12-6) 45 | 46 | * new directive, places-auto-complete 47 | 48 | # 0.10.0 (2016-12-6) 49 | 50 | * new directive, data-layer, street-view-panorama 51 | * little re-factoring on build 52 | 53 | # 0.9.0 (2016-11-22) 54 | 55 | * new directive, polyline, ground-overlay 56 | * layer directives, traffic-layer, transit-layer, heatmap-layer, bicycling-layer, kml-layer 57 | 58 | # 0.8.0 (2016-11-16) 59 | 60 | * added Polygon Directive 61 | * [options] property is added for all directives 62 | * added BaseMapDirective for the super class of all map directives 63 | 64 | # 0.7.0 (2016-11-06) 65 | 66 | * Added Circle Directive (Thanks to Abdellatif Ait boudad) 67 | 68 | -------------------------------------------------------------------------------- /app/map-components/custom-marker-ng-for.component.ts: -------------------------------------------------------------------------------- 1 | import { of } from 'rxjs'; 2 | import { Component } from '@angular/core'; 3 | import { SourceCodeService } from '../source-code.service'; 4 | 5 | 6 | @Component({ 7 | template: ` 8 |

Custom Marker With *ngFor

9 | 10 | 11 |
{{count}}
12 |
13 |
14 |
15 |
16 |
17 | 18 |
{{code}}
19 | `, 20 | styles: [ 21 | ` 22 | .custom-icon { 23 | width: 30px; 24 | height: 30px; 25 | border-radius: 50%; 26 | background-color:blue; 27 | border: 2px solid white; 28 | color:white; 29 | font-size:20px; 30 | text-align:center; 31 | } 32 | `, 33 | ] 34 | }) 35 | export class CustomMarkerNgForComponent { 36 | public positions = []; 37 | public count: number = 0; 38 | public code: string; 39 | 40 | constructor(public sc: SourceCodeService) { 41 | this.positions = this.getRandomMarkers(); 42 | sc.getText('CustomMarkerNgForComponent').subscribe(text => this.code = text); 43 | } 44 | 45 | getRandomMarkers() { 46 | let randomLat: number, randomLng: number; 47 | 48 | let positions = []; 49 | for (let i = 0 ; i < 9; i++) { 50 | randomLat = Math.random() * (43.7399 - 43.7300) + 43.7300; 51 | randomLng = Math.random() * (-79.7600 - -79.7699) + -79.7699; 52 | positions.push([randomLat, randomLng]); 53 | } 54 | return positions; 55 | } 56 | 57 | showMarkersFromObservable() { 58 | of(this.getRandomMarkers()) // Think this as http call 59 | .subscribe( positions => { 60 | this.positions = positions; 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/map-components/drawing-manager.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild, OnInit } from '@angular/core'; 2 | import { DrawingManager } from '@ngui/map'; 3 | import { SourceCodeService } from '../source-code.service'; 4 | 5 | @Component({ 6 | template: ` 7 |

Drawing Manager

8 | 9 | 23 | 24 | selectedOverlay: {{selectedOverlay}}
25 | 26 | 27 | 28 | 29 |
{{code}}
30 | `}) 31 | export class DrawingManagerComponent implements OnInit { 32 | code: string; 33 | selectedOverlay: any; 34 | @ViewChild(DrawingManager) drawingManager: DrawingManager; 35 | 36 | constructor(public sc: SourceCodeService) { 37 | sc.getText('DrawingManagerComponent').subscribe(text => this.code = text); 38 | } 39 | 40 | ngOnInit() { 41 | this.drawingManager['initialized$'].subscribe(dm => { 42 | google.maps.event.addListener(dm, 'overlaycomplete', event => { 43 | if (event.type !== google.maps.drawing.OverlayType.MARKER) { 44 | dm.setDrawingMode(null); 45 | google.maps.event.addListener(event.overlay, 'click', e => { 46 | this.selectedOverlay = event.overlay; 47 | this.selectedOverlay.setEditable(true); 48 | }); 49 | this.selectedOverlay = event.overlay; 50 | } 51 | }); 52 | }); 53 | } 54 | 55 | deleteSelectedOverlay() { 56 | if (this.selectedOverlay) { 57 | this.selectedOverlay.setMap(null); 58 | delete this.selectedOverlay; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/services/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * return json string from json-like string 3 | */ 4 | export function jsonize(str: string): string { 5 | try { // if parsable already, return as it is 6 | JSON.parse(str); 7 | return str; 8 | } catch (e) { // if not parsable, change little 9 | return str 10 | .replace(/([\$\w]+)\s*:/g, // wrap keys without double quote 11 | function(_: any, $1: any) { 12 | return '"' + $1 + '":'; 13 | } 14 | ) 15 | .replace(/'([^']+)'/g, // replacing single quote to double quote 16 | function(_: any, $1: any) { 17 | return '"' + $1 + '"'; 18 | } 19 | ); 20 | } 21 | } 22 | 23 | /** 24 | * Returns string to an object by using JSON.parse() 25 | */ 26 | export function getJSON(input: any): any { 27 | if (typeof input === 'string') { 28 | const re = /^[\+\-]?[0-9\.]+,[ ]*\ ?[\+\-]?[0-9\.]+$/; // lat,lng 29 | if (input.match(re)) { 30 | input = '[' + input + ']'; 31 | } 32 | return JSON.parse(jsonize(input)); 33 | } else { 34 | return input; 35 | } 36 | } 37 | 38 | /** 39 | * json type definition 40 | */ 41 | /* tslint:disable */ 42 | //interface IJsonArray extends Array { } 43 | export interface IJson { 44 | //[x: string]: string|number|boolean|Date|IJson|IJsonArray; 45 | [x: string]: string|number|boolean|Date|IJson|Array; 46 | } 47 | /* tslint:enable */ 48 | 49 | 50 | /** 51 | * Returns camel-cased from string 'Foo Bar' to 'fooBar' 52 | */ 53 | export function toCamelCase(str: string): string { 54 | return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) { 55 | return index === 0 ? letter.toLowerCase() : letter.toUpperCase(); 56 | }).replace(/\s+/g, ''); 57 | } 58 | 59 | export function isMapsApiLoaded() { 60 | return typeof google === 'object' && typeof google.maps === 'object'; 61 | } 62 | 63 | export function missingLibraryError(component, libName) { 64 | return Error(`${component}: library '${libName}' is missing, please ensure to include it in a 'libraries' parameter. 65 | Example: 66 | NguiMapModule.forRoot({ 67 | apiUrl: 'https://maps.googleapis.com/maps/api/js?libraries=${libName}' 68 | }) 69 | `); 70 | } -------------------------------------------------------------------------------- /ngc-pre-compiler.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | /** 5 | * this is to declare all INPUTS as a public variable for components and directives 6 | * Without declaring all INPUTS variable, there will be an error in AoT compilation 7 | * https://github.com/ng2-ui/map/pull/64 8 | */ 9 | var path = require("path"); 10 | var fs = require('fs-extra'); 11 | 12 | var tmpDir = path.join('tmp'); 13 | fs.ensureDirSync(tmpDir); 14 | fs.emptyDirSync(tmpDir); 15 | fs.copySync('src', tmpDir); 16 | 17 | ['components', 'directives', 'services'].forEach(dirName => { 18 | let dirPath = path.join(tmpDir, dirName); 19 | let files = fs.readdirSync(dirPath); 20 | let js; 21 | files.forEach(fileName => { 22 | let jsPath = path.join(dirPath, fileName); 23 | 24 | js = fs.readFileSync(jsPath, 'utf8'); 25 | // https://github.com/yahoo/strip-loader/blob/master/lib/index.js#L17 26 | let regexPattern = new RegExp('(?:^|\\n)[ \\t]*(console.log)\\(((\\"[^\\"]*\\")|(\\\'[^\\\']*\\\')|[^\\);]|\\([^\\);]*\\))*\\)[ \\t]*(?:$|[;\\n])', 'g'); 27 | fs.writeFileSync(jsPath, js.replace(regexPattern, '\n')); 28 | 29 | js = fs.readFileSync(jsPath, 'utf8'); 30 | let outputMatches = js.match(/OUTPUTS\s*=\s*(\[[^\]]*?\])/m); 31 | if (outputMatches) { 32 | let outputs = eval(outputMatches[1]); 33 | let outputsJs = '\n // declare OUTPUTS for AOT compiler\n' + 34 | outputs.map(input => ' public ' + input + ': any; // generated by '+path.basename(__filename)).join('\n') + '\n'; 35 | let replaceMatches = js.match(/(@Component|@Directive)\([\s\S]+?OUTPUTS[\s\S]+? class [\s\S]+?\{/m); 36 | if (replaceMatches) { 37 | let jsToReplace = js.replace(replaceMatches[0], $0 => $0 + outputsJs); 38 | fs.writeFileSync(jsPath, jsToReplace); 39 | } 40 | } 41 | 42 | js = fs.readFileSync(jsPath, 'utf8'); 43 | let inputMatches = js.match(/INPUTS\s*=\s*(\[[^\]]*?\])/m); 44 | if (inputMatches) { 45 | let inputs = eval(inputMatches[1]); 46 | let inputsJs = '\n // declare INPUTS for AOT compiler\n'+ 47 | inputs.map(input => ' public ' + input + ': any; // generated by '+path.basename(__filename)).join('\n') + 48 | '\n'; 49 | let replaceMatches = js.match(/(@Component|@Directive)\([\s\S]+?INPUTS[\s\S]+? class [\s\S]+?\{/m); 50 | if (replaceMatches) { 51 | let jsToReplace = js.replace(replaceMatches[0], $0 => $0 + inputsJs); 52 | fs.writeFileSync(jsPath, jsToReplace); 53 | } 54 | } 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/directives/circle.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 7 | 'center', 'clickable', 'draggable', 'editable', 'fillColor', 'fillOpacity', 'map', 'radius', 8 | 'strokeColor', 'strokeOpacity', 'strokePosition', 'strokeWeight', 'visible', 'zIndex', 'options', 9 | // ngui-map specific inputs 10 | 'geoFallbackCenter' 11 | ]; 12 | const OUTPUTS = [ 13 | 'centerChanged', 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 14 | 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'radiusChanged', 'rightclick', 15 | ]; 16 | 17 | @Directive({ 18 | selector: 'ngui-map>circle, ngui-map>map-circle', 19 | inputs: INPUTS, 20 | outputs: OUTPUTS, 21 | }) 22 | export class Circle extends BaseMapDirective { 23 | public mapObject: google.maps.Circle; 24 | public objectOptions: google.maps.CircleOptions = {}; 25 | 26 | constructor(private nguiMapComp: NguiMapComponent) { 27 | super(nguiMapComp, 'Circle', INPUTS, OUTPUTS); 28 | } 29 | 30 | initialize(): void { 31 | super.initialize(); 32 | this.setCenter(); 33 | } 34 | 35 | setCenter(): void { 36 | if (!this['center']) { 37 | this._subscriptions.push(this.nguiMapComp.geolocation.getCurrentPosition().subscribe( 38 | center => { 39 | console.log('setting circle center from current location'); 40 | let latLng = new google.maps.LatLng(center.coords.latitude, center.coords.longitude); 41 | this.mapObject.setCenter(latLng); 42 | }, 43 | error => { 44 | console.error('ngui-map, error in finding the current position'); 45 | this.mapObject.setCenter(this.objectOptions['geoFallbackCenter'] || new google.maps.LatLng(0, 0)); 46 | } 47 | )); 48 | } else if (typeof this['center'] === 'string') { 49 | this._subscriptions.push(this.nguiMapComp.geoCoder.geocode({address: this['center']}).subscribe( 50 | results => { 51 | console.log('setting circle center from address', this['center']); 52 | this.mapObject.setCenter(results[0].geometry.location); 53 | }, 54 | error => { 55 | console.error('ngui-map, error in finding location from', this['center']); 56 | this.mapObject.setCenter(this.objectOptions['geoFallbackCenter'] || new google.maps.LatLng(0, 0)); 57 | } 58 | )); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ngui-map.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { OptionBuilder } from './services/option-builder'; 5 | import { GeoCoder } from './services/geo-coder'; 6 | import { NavigatorGeolocation } from './services/navigator-geolocation'; 7 | import { NG_MAP_CONFIG_TOKEN, ConfigOption } from './services/config'; 8 | import { NgMapApiLoader, NgMapAsyncCallbackApiLoader } from './services/api-loader'; 9 | 10 | import { NguiMapComponent } from './components/ngui-map.component'; 11 | import { InfoWindow } from './components/info-window'; 12 | import { CustomMarker } from './components/custom-marker'; 13 | 14 | import { BicyclingLayer } from './directives/bicycling-layer'; 15 | import { Circle } from './directives/circle'; 16 | import { DataLayer } from './directives/data-layer'; 17 | import { DirectionsRenderer } from './directives/directions-renderer'; 18 | import { DrawingManager } from './directives/drawing-manager'; 19 | import { GroundOverlay } from './directives/ground-overlay'; 20 | import { HeatmapLayer } from './directives/heatmap-layer'; 21 | import { KmlLayer } from './directives/kml-layer'; 22 | import { Marker } from './directives/marker'; 23 | import { NguiMap } from './services/ngui-map'; 24 | import { PlacesAutoComplete } from './directives/places-auto-complete'; 25 | import { Polygon } from './directives/polygon'; 26 | import { Polyline } from './directives/polyline'; 27 | import { StreetViewPanorama } from './directives/street-view-panorama'; 28 | import { TrafficLayer } from './directives/traffic-layer'; 29 | import { TransitLayer } from './directives/transit-layer'; 30 | 31 | const COMPONENTS_DIRECTIVES = [ 32 | NguiMapComponent, InfoWindow, 33 | Marker, Circle, CustomMarker, Polygon, InfoWindow, Polyline, GroundOverlay, 34 | TransitLayer, TrafficLayer, HeatmapLayer, BicyclingLayer, KmlLayer, DataLayer, 35 | StreetViewPanorama, PlacesAutoComplete, DirectionsRenderer, 36 | DrawingManager, 37 | ]; 38 | 39 | @NgModule({ 40 | imports: [ CommonModule ], 41 | declarations: COMPONENTS_DIRECTIVES, 42 | exports: [COMPONENTS_DIRECTIVES], 43 | providers: [ 44 | GeoCoder, 45 | NavigatorGeolocation, 46 | NguiMap, 47 | OptionBuilder, 48 | {provide: NgMapApiLoader, useClass: NgMapAsyncCallbackApiLoader}, 49 | ] 50 | }) 51 | export class NguiMapModule { 52 | static forRoot(config: ConfigOption = {}): ModuleWithProviders { 53 | return { 54 | ngModule: NguiMapModule, 55 | providers: [ 56 | { provide: NG_MAP_CONFIG_TOKEN, useValue: config } 57 | ], 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/map-components/directions-renderer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild, ChangeDetectorRef, OnInit } from '@angular/core'; 2 | import { DirectionsRenderer } from '@ngui/map'; 3 | import { SourceCodeService } from '../source-code.service'; 4 | 5 | @Component({ 6 | template: ` 7 |

Directions Renderer

8 |
9 | Start: 10 | 15 | End: 16 | 21 |
22 | {{direction | json}} 23 | 24 | 30 | 31 | 32 |
33 | 34 | 35 | 36 |
{{code}}
37 | `}) 38 | export class DirectionsRendererComponent implements OnInit { 39 | @ViewChild(DirectionsRenderer) directionsRendererDirective: DirectionsRenderer; 40 | 41 | code: string; 42 | directionsRenderer: google.maps.DirectionsRenderer; 43 | directionsResult: google.maps.DirectionsResult; 44 | direction: any = { 45 | origin: 'penn station, new york, ny', 46 | destination: '260 Broadway New York NY 10007', 47 | travelMode: 'WALKING' 48 | }; 49 | 50 | constructor(private cdr: ChangeDetectorRef, public sc: SourceCodeService) { 51 | sc.getText('DirectionsRendererComponent').subscribe(text => this.code = text); 52 | } 53 | 54 | ngOnInit() { 55 | this.directionsRendererDirective['initialized$'].subscribe( directionsRenderer => { 56 | this.directionsRenderer = directionsRenderer; 57 | }); 58 | } 59 | 60 | directionsChanged() { 61 | this.directionsResult = this.directionsRenderer.getDirections(); 62 | this.cdr.detectChanges(); 63 | } 64 | 65 | showDirection() { 66 | this.directionsRendererDirective['showDirections'](this.direction); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/directives/street-view-panorama.ts: -------------------------------------------------------------------------------- 1 | import {Directive, OnDestroy} from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 7 | 'selector', 'options', 8 | 'addressControl', 'addressControlOptions', 'clickToGo', 'disableDefaultUI', 'disableDoubleClickZoom', 9 | 'enableCloseButton', 'fullscreenControl', 'fullscreenControlOptions', 'imageDateControl', 'linksControl', 10 | 'motionTracking', 'motionTrackingControl', 'panControl', 'panControlOptions', 'pano', 11 | 'position', 'pov', 'scrollwheel', 'showRoadLabels', 'visible', 'zoomControl', 'zoomControlOptions' 12 | ]; 13 | const OUTPUTS = [ 14 | 'closeclick', 'pano_changed', 'position_changed', 'pov_changed', 'resize', 'status_changed', 15 | 'visible_changed', 'zoom_changed' 16 | ]; 17 | 18 | @Directive({ 19 | selector: 'ngui-map > street-view-panorama', 20 | inputs: INPUTS, 21 | outputs: OUTPUTS, 22 | }) 23 | export class StreetViewPanorama extends BaseMapDirective implements OnDestroy { 24 | constructor(nguiMapComp: NguiMapComponent) { 25 | super(nguiMapComp, 'StreetViewPanorama', INPUTS, OUTPUTS); 26 | } 27 | 28 | // only called when map is ready 29 | initialize(): void { 30 | this.objectOptions = this.optionBuilder.googlizeAllInputs(this.inputs, this); 31 | console.log(this.mapObjectName, 'initialization objectOptions', this.objectOptions); 32 | 33 | let element: HTMLElement; 34 | if (this.objectOptions.selector) { 35 | // noinspection TypeScriptValidateTypes 36 | element = document.querySelector(this['selector']); 37 | delete this.objectOptions.selector; 38 | } else { 39 | element = this.nguiMapComponent.el; 40 | } 41 | 42 | // will be set after geocoded 43 | typeof this.objectOptions.position === 'string' && (delete this.objectOptions.position); 44 | 45 | this.mapObject = new google.maps[this.mapObjectName](element, this.objectOptions); 46 | this.mapObject['mapObjectName'] = this.mapObjectName; 47 | this.mapObject['nguiMapComponent'] = this.nguiMapComponent; 48 | 49 | // set google events listeners and emits to this outputs listeners 50 | this.nguiMap.setObjectEvents(this.outputs, this, 'mapObject'); 51 | 52 | this.nguiMapComponent.addToMapObjectGroup(this.mapObjectName, this.mapObject); 53 | this.initialized$.emit(this.mapObject); 54 | } 55 | 56 | // When destroyed, remove event listener, and delete this object to prevent memory leak 57 | ngOnDestroy() { 58 | if (this.nguiMapComponent.el) { 59 | this.nguiMap.clearObjectEvents(this.outputs, this, 'mapObject'); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngui/map", 3 | "version": "0.30.1", 4 | "description": "Angular2 Google Map`", 5 | "license": "MIT", 6 | "main": "dist/bundles/map.umd.js", 7 | "module": "dist/@ngui/map.es5.js", 8 | "typings": "dist/map.d.ts", 9 | "scripts": { 10 | "start": "ng serve --port 9001 --open", 11 | "lint": "tslint 'src/**/*.ts' 'app/**/*.ts'", 12 | "lint-fix": "tslint --fix 'src/**/*.ts' 'app/**/*.ts' -p tsconfig.json", 13 | "clean": "rimraf dist", 14 | "build": "npm-run-all --serial clean build:ngc:pre build:ngc", 15 | "build:ngc:pre": "node ngc-pre-compiler.js", 16 | "build:ngc": "ng-packagr -p ng-package.json", 17 | "gh-pages": "ng build --prod --no-aot --base-href=\"/map/\" && ngh --dir dist/app --repo=https://GH_TOKEN@github.com/ng2-ui/map.git", 18 | "test": "npm-run-all --serial test:start test:webtest test:stop", 19 | "test:start": "forever start --silent node_modules/.bin/ng serve --port 9239", 20 | "test:stop": "forever stopall", 21 | "test:webtest": "webtest webtest.txt", 22 | "upgrade": "npm-check-updates -a/--upgradeAll && npm i" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/ng2-ui/map.git" 27 | }, 28 | "author": "Allen Kim", 29 | "bugs": { 30 | "url": "https://github.com/ng2-ui/map/issues" 31 | }, 32 | "homepage": "https://github.com/ng2-ui/map#readme", 33 | "keywords": [ 34 | "angular", 35 | "google", 36 | "map" 37 | ], 38 | "dependencies": { 39 | "tslib": "^1.7.1" 40 | }, 41 | "peerDependencies": { 42 | "@angular/common": ">=5.0.0", 43 | "rxjs": ">=6.0.0" 44 | }, 45 | "devDependencies": { 46 | "@angular-devkit/build-angular": "~0.6.1", 47 | "@angular/cli": "6.0.1", 48 | "@angular/common": "6.0.2", 49 | "@angular/compiler": "6.0.2", 50 | "@angular/compiler-cli": "6.0.2", 51 | "@angular/core": "6.0.2", 52 | "@angular/forms": "6.0.2", 53 | "@angular/http": "6.0.2", 54 | "@angular/platform-browser": "6.0.2", 55 | "@angular/platform-browser-dynamic": "6.0.2", 56 | "@angular/router": "6.0.2", 57 | "@ngui/utils": "^0.8.1", 58 | "@types/googlemaps": "^3.30.0", 59 | "@types/hammerjs": "^2.0.34", 60 | "@types/node": "^10.1.0", 61 | "angular-cli-ghpages": "^0.5.1", 62 | "codelyzer": "^4.0.1", 63 | "core-js": "^2.4.1", 64 | "create-plunker": "^1.3.0", 65 | "forever": "^0.15.3", 66 | "fs-extra": "^6.0.1", 67 | "ng-packagr": "~2.4.2", 68 | "npm-run-all": "^4.1.2", 69 | "rimraf": "^2.6.1", 70 | "rxjs": "^6.1.0", 71 | "tslint": "^5.8.0", 72 | "typescript": "~2.7.2", 73 | "webtest": "^0.8.3", 74 | "zone.js": "^0.8.26" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/services/api-loader.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject, Optional, NgZone, OnDestroy } from '@angular/core'; 2 | import { ReplaySubject } from 'rxjs'; 3 | import { NG_MAP_CONFIG_TOKEN } from './config'; 4 | import { isMapsApiLoaded } from './util'; 5 | 6 | export abstract class NgMapApiLoader implements OnDestroy { 7 | api$: ReplaySubject = new ReplaySubject(1); 8 | 9 | abstract load(); 10 | 11 | constructor(protected config) { 12 | this.config = this.config || {apiUrl: 'https://maps.google.com/maps/api/js'}; 13 | } 14 | 15 | ngOnDestroy() { 16 | this.api$.complete(); 17 | } 18 | } 19 | 20 | @Injectable() 21 | export class NgMapAsyncCallbackApiLoader extends NgMapApiLoader { 22 | constructor(protected zone: NgZone, @Optional() @Inject(NG_MAP_CONFIG_TOKEN) config) { 23 | super(config); 24 | } 25 | 26 | load() { 27 | if (typeof window === 'undefined') { 28 | return; 29 | } 30 | 31 | if (isMapsApiLoaded()) { 32 | this.api$.next(google.maps); 33 | } else if (!document.querySelector('#ngui-map-api')) { 34 | (window)['nguiMapRef'] = (window)['nguiMapRef'] || []; 35 | (window)['nguiMapRef'].push({ zone: this.zone, componentFn: () => this.api$.next(google.maps)}); 36 | this.addGoogleMapsApi(); 37 | } 38 | } 39 | 40 | private addGoogleMapsApi() { 41 | (window)['initNguiMap'] = (window)['initNguiMap'] || function() { 42 | (window)['nguiMapRef'].forEach(nguiMapRef => { 43 | nguiMapRef.zone.run(function() { nguiMapRef.componentFn(); }); 44 | }); 45 | (window)['nguiMapRef'].splice(0, (window)['nguiMapRef'].length); 46 | }; 47 | 48 | let script = document.createElement( 'script' ); 49 | script.id = 'ngui-map-api'; 50 | 51 | // script.src = "https://maps.google.com/maps/api/js?callback=initNguiMap"; 52 | let apiUrl = this.config.apiUrl ; 53 | apiUrl += apiUrl.indexOf('?') !== -1 ? '&' : '?'; 54 | script.src = apiUrl + 'callback=initNguiMap'; 55 | document.querySelector('body').appendChild(script); 56 | } 57 | } 58 | 59 | @Injectable() 60 | export class NgMapAsyncApiLoader extends NgMapApiLoader { 61 | constructor(@Optional() @Inject(NG_MAP_CONFIG_TOKEN) config) { 62 | super(config); 63 | } 64 | 65 | load() { 66 | if (typeof window === 'undefined') { 67 | return; 68 | } 69 | 70 | if (isMapsApiLoaded()) { 71 | this.api$.next(google.maps); 72 | } else if (!document.querySelector('#ngui-map-api')) { 73 | let script = document.createElement('script'); 74 | script.id = 'ngui-map-api'; 75 | 76 | script.async = true; 77 | script.onload = () => this.api$.next(google.maps); 78 | script.src = this.config.apiUrl; 79 | document.querySelector('body').appendChild(script); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | import 'core-js/es6/symbol'; 23 | import 'core-js/es6/object'; 24 | import 'core-js/es6/function'; 25 | import 'core-js/es6/parse-int'; 26 | import 'core-js/es6/parse-float'; 27 | import 'core-js/es6/number'; 28 | import 'core-js/es6/math'; 29 | import 'core-js/es6/string'; 30 | import 'core-js/es6/date'; 31 | import 'core-js/es6/array'; 32 | import 'core-js/es6/regexp'; 33 | import 'core-js/es6/map'; 34 | import 'core-js/es6/weak-map'; 35 | import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | 56 | 57 | /*************************************************************************************************** 58 | * Zone JS is required by Angular itself. 59 | */ 60 | import 'zone.js/dist/zone'; // Included with Angular CLI. 61 | 62 | 63 | 64 | /*************************************************************************************************** 65 | * APPLICATION IMPORTS 66 | */ 67 | 68 | /** 69 | * Date, currency, decimal and percent pipes. 70 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 71 | */ 72 | // import 'intl'; // Run `npm install --save intl`. 73 | /** 74 | * Need to import at least one locale-data with intl. 75 | */ 76 | // import 'intl/locale-data/jsonp/en'; -------------------------------------------------------------------------------- /src/directives/marker.ts: -------------------------------------------------------------------------------- 1 | import {Directive, OnInit} from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | 6 | const INPUTS = [ 7 | 'anchorPoint', 'animation', 'clickable', 'cursor', 'draggable', 'icon', 'label', 'opacity', 8 | 'optimized', 'place', 'position', 'shape', 'title', 'visible', 'zIndex', 'options', 9 | // ngui-map specific inputs 10 | 'geoFallbackPosition' 11 | ]; 12 | const OUTPUTS = [ 13 | 'animationChanged', 'click', 'clickableChanged', 'cursorChanged', 'dblclick', 'drag', 'dragend', 'draggableChanged', 14 | 'dragstart', 'flatChanged', 'iconChanged', 'mousedown', 'mouseout', 'mouseover', 'mouseup', 'positionChanged', 'rightclick', 15 | 'shapeChanged', 'titleChanged', 'visibleChanged', 'zindexChanged' 16 | ]; 17 | 18 | @Directive({ 19 | selector: 'ngui-map > marker', 20 | inputs: INPUTS, 21 | outputs: OUTPUTS, 22 | }) 23 | export class Marker extends BaseMapDirective implements OnInit { 24 | public mapObject: google.maps.Marker; 25 | public objectOptions: google.maps.MarkerOptions = {}; 26 | 27 | constructor(private nguiMapComp: NguiMapComponent) { 28 | super(nguiMapComp, 'Marker', INPUTS, OUTPUTS); 29 | console.log('marker constructor', 9999999 ); 30 | } 31 | 32 | // Initialize this map object when map is ready 33 | ngOnInit() { 34 | if (this.nguiMapComponent.mapIdledOnce) { // map is ready already 35 | this.initialize(); 36 | } else { 37 | this.nguiMapComponent.mapReady$.subscribe(map => this.initialize()); 38 | } 39 | } 40 | 41 | initialize(): void { 42 | super.initialize(); 43 | this.setPosition(); 44 | } 45 | 46 | setPosition(): void { 47 | if (!this['position']) { 48 | this._subscriptions.push(this.nguiMapComp.geolocation.getCurrentPosition().subscribe( 49 | position => { 50 | console.log('setting marker position from current location'); 51 | let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); 52 | this.mapObject.setPosition(latLng); 53 | }, 54 | error => { 55 | console.error('ngui-map, error finding the current location'); 56 | this.mapObject.setPosition(this.objectOptions['geoFallbackPosition'] || new google.maps.LatLng(0, 0)); 57 | } 58 | )); 59 | } else if (typeof this['position'] === 'string') { 60 | this._subscriptions.push(this.nguiMapComp.geoCoder.geocode({address: this['position']}).subscribe( 61 | results => { 62 | console.log('setting marker position from address', this['position']); 63 | this.mapObject.setPosition(results[0].geometry.location); 64 | }, 65 | error => { 66 | console.error('ngui-map, error finding the location from', this['position']); 67 | this.mapObject.setPosition(this.objectOptions['geoFallbackPosition'] || new google.maps.LatLng(0, 0)); 68 | } 69 | )); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/services/ngui-map.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, SimpleChanges, NgZone } from '@angular/core'; 2 | import { OptionBuilder } from './option-builder'; 3 | import { GeoCoder } from './geo-coder'; 4 | 5 | /** 6 | * collection of map instance-related properties and methods 7 | */ 8 | @Injectable() 9 | export class NguiMap { 10 | 11 | constructor( 12 | private geoCoder: GeoCoder, 13 | private optionBuilder: OptionBuilder, 14 | private zone: NgZone, 15 | ) {} 16 | 17 | setObjectEvents(definedEvents: string[], thisObj: any, prefix: string) { 18 | definedEvents.forEach(definedEvent => { 19 | const eventName = this.getEventName(definedEvent), 20 | zone = this.zone; 21 | zone.runOutsideAngular(() => { 22 | thisObj[prefix].addListener(eventName, function(event: google.maps.event) { 23 | let param: any = event ? event : {}; 24 | param.target = this; 25 | zone.run(() => thisObj[definedEvent].emit(param)); 26 | }); 27 | }); 28 | }); 29 | } 30 | 31 | clearObjectEvents(definedEvents: string[], thisObj: any, prefix: string) { 32 | definedEvents.forEach(definedEvent => { 33 | const eventName = this.getEventName(definedEvent); 34 | this.zone.runOutsideAngular(() => { 35 | if (thisObj[prefix]) { 36 | google.maps.event.clearListeners(thisObj[prefix], eventName); 37 | } 38 | }); 39 | }); 40 | 41 | if (thisObj[prefix]) { 42 | if (thisObj[prefix].setMap) { 43 | thisObj[prefix].setMap(null); 44 | } 45 | 46 | delete thisObj[prefix].nguiMapComponent; 47 | delete thisObj[prefix]; 48 | } 49 | 50 | } 51 | 52 | updateGoogleObject = (object: any, changes: SimpleChanges) => { 53 | let val: any, currentValue: any, setMethodName: string; 54 | if (object) { 55 | for (let key in changes) { 56 | setMethodName = `set${key.replace(/^[a-z]/, x => x.toUpperCase()) }`; 57 | currentValue = changes[key].currentValue; 58 | if (['position', 'center'].indexOf(key) !== -1 && typeof currentValue === 'string') { 59 | // To preserve setMethod name in Observable callback, wrap it as a function, then execute 60 | ((setMethodName) => { 61 | this.geoCoder.geocode({address: currentValue}).subscribe(results => { 62 | if (typeof object[setMethodName] === 'function') { 63 | object[setMethodName](results[0].geometry.location); 64 | } else { 65 | console.error( 66 | 'Not all options are dynamically updatable according to Googles Maps API V3 documentation.\n' + 67 | 'Please check Google Maps API documentation, and use "setOptions" instead.' 68 | ); 69 | } 70 | }); 71 | })(setMethodName); 72 | } else { 73 | val = this.optionBuilder.googlize(currentValue); 74 | if (typeof object[setMethodName] === 'function') { 75 | object[setMethodName](val); 76 | } else { 77 | console.error( 78 | 'Not all options are dynamically updatable according to Googles Maps API V3 documentation.\n' + 79 | 'Please check Google Maps API documentation, and use "setOptions" instead.' 80 | ); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | private getEventName(definedEvent) { 88 | return definedEvent 89 | .replace(/([A-Z])/g, ($1) => `_${$1.toLowerCase()}`) // positionChanged -> position_changed 90 | .replace(/^map_/, ''); // map_click -> click to avoid DOM conflicts 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/map-components/heatmap-layer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild, OnInit } from '@angular/core'; 2 | import { HeatmapLayer } from '@ngui/map'; 3 | import { SourceCodeService } from '../source-code.service'; 4 | 5 | @Component({ 6 | template: ` 7 |

Heatmap Layer

8 | 9 |
10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 |
19 | 20 | 21 | 22 | 23 |
{{code}}
24 | `, 25 | styles: [` 26 | #floating-panel { 27 | position: absolute; 28 | background-color: #fff; 29 | border: 1px solid #999; 30 | font-family: 'Roboto','sans-serif'; 31 | left: 25%; 32 | line-height: 30px; 33 | padding: 5px; 34 | padding-left: 10px; 35 | text-align: center; 36 | top: 10px; 37 | z-index: 5; 38 | } 39 | `] 40 | }) 41 | export class HeatmapLayerComponent implements OnInit { 42 | @ViewChild(HeatmapLayer) heatmapLayer: HeatmapLayer; 43 | heatmap: google.maps.visualization.HeatmapLayer; 44 | map: google.maps.Map; 45 | points = []; 46 | code: string; 47 | 48 | constructor(public sc: SourceCodeService) { 49 | sc.getText('HeatmapLayerComponent').subscribe(text => this.code = text); 50 | } 51 | 52 | ngOnInit() { 53 | this.heatmapLayer['initialized$'].subscribe(heatmap => { 54 | this.points = [ 55 | new google.maps.LatLng(37.782551, -122.445368), 56 | new google.maps.LatLng(37.782745, -122.444586), 57 | new google.maps.LatLng(37.782842, -122.443688) 58 | ]; 59 | this.heatmap = heatmap; 60 | this.map = this.heatmap.getMap(); 61 | }); 62 | } 63 | 64 | toggleHeatmap() { 65 | this.heatmap.setMap(this.heatmap.getMap() ? null : this.map); 66 | } 67 | 68 | changeGradient() { 69 | let gradient = [ 70 | 'rgba(0, 255, 255, 0)', 71 | 'rgba(0, 255, 255, 1)', 72 | 'rgba(0, 191, 255, 1)', 73 | 'rgba(0, 127, 255, 1)', 74 | 'rgba(0, 63, 255, 1)', 75 | 'rgba(0, 0, 255, 1)', 76 | 'rgba(0, 0, 223, 1)', 77 | 'rgba(0, 0, 191, 1)', 78 | 'rgba(0, 0, 159, 1)', 79 | 'rgba(0, 0, 127, 1)', 80 | 'rgba(63, 0, 91, 1)', 81 | 'rgba(127, 0, 63, 1)', 82 | 'rgba(191, 0, 31, 1)', 83 | 'rgba(255, 0, 0, 1)' 84 | ]; 85 | this.heatmap.set('gradient', this.heatmap.get('gradient') ? null : gradient); 86 | } 87 | 88 | changeRadius() { 89 | this.heatmap.set('radius', this.heatmap.get('radius') ? null : 20); 90 | } 91 | 92 | changeOpacity() { 93 | this.heatmap.set('opacity', this.heatmap.get('opacity') ? null : 0.2); 94 | } 95 | 96 | loadRandomPoints() { 97 | this.points = []; 98 | 99 | for (let i = 0 ; i < 9; i++) { 100 | this.addPoint(); 101 | } 102 | } 103 | 104 | addPoint() { 105 | let randomLat = Math.random() * 0.0099 + 37.782551; 106 | let randomLng = Math.random() * 0.0099 + -122.445368; 107 | let latlng = new google.maps.LatLng(randomLat, randomLng); 108 | this.points.push(latlng); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/directives/directions-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Input, Directive, SimpleChanges, OnChanges, OnDestroy } from '@angular/core'; 2 | 3 | import { BaseMapDirective } from './base-map-directive'; 4 | import { NguiMapComponent } from '../components/ngui-map.component'; 5 | import { NavigatorGeolocation } from '../services/navigator-geolocation'; 6 | 7 | const INPUTS = [ 8 | 'directions', 'draggable', 'hideRouteList', 'infoWindow', 'panel', 'markerOptions', 9 | 'polylineOptions', 'preserveViewport', 'routeIndex', 'suppressBicyclingLayer', 10 | 'suppressInfoWindows', 'suppressMarkers', 'suppressPolylines' 11 | ]; 12 | const OUTPUTS = ['directions_changed']; 13 | 14 | @Directive({ 15 | selector: 'ngui-map > directions-renderer', 16 | inputs: INPUTS, 17 | outputs: OUTPUTS, 18 | }) 19 | export class DirectionsRenderer extends BaseMapDirective implements OnChanges, OnDestroy { 20 | // tslint:disable-next-line 21 | @Input('directions-request') directionsRequest: google.maps.DirectionsRequest; 22 | 23 | directionsService: google.maps.DirectionsService; 24 | directionsRenderer: google.maps.DirectionsRenderer; 25 | 26 | constructor( 27 | nguiMapComponent: NguiMapComponent, 28 | public geolocation: NavigatorGeolocation 29 | ) { 30 | super(nguiMapComponent, 'DirectionsRenderer', INPUTS, OUTPUTS); 31 | } 32 | 33 | // only called when map is ready 34 | initialize(): void { 35 | this.objectOptions = this.optionBuilder.googlizeAllInputs(this.inputs, this); 36 | if (typeof this.objectOptions['panel'] === 'string') { // find a Node for panel 37 | this.objectOptions['panel'] = document.querySelector(this.objectOptions['panel']); 38 | } 39 | 40 | console.log('DirectionsRenderer', 'initialization options', this.objectOptions, this.directionsRequest); 41 | 42 | this.directionsService = new google.maps.DirectionsService(); 43 | this.directionsRenderer = new google.maps.DirectionsRenderer(this.objectOptions); 44 | 45 | this.directionsRenderer.setMap(this.nguiMapComponent.map); 46 | 47 | // set google events listeners and emidirectionsRenderer to this outputs listeners 48 | this.showDirections(this.directionsRequest); 49 | 50 | this.nguiMap.setObjectEvents(this.outputs, this, 'directionsRenderer'); 51 | 52 | this.nguiMapComponent.addToMapObjectGroup(this.mapObjectName, this.mapObject); 53 | this.initialized$.emit(this.directionsRenderer); 54 | } 55 | 56 | 57 | ngOnChanges(changes: SimpleChanges) { 58 | let newOptions = {}; 59 | for (let key in changes) { 60 | if (this.inputs.indexOf(key) !== -1) { 61 | newOptions[key] = this.optionBuilder.googlize(changes[key].currentValue); 62 | } 63 | } 64 | if (changes['directionsRequest'] && this.directionsRenderer) { 65 | this.directionsService && this.showDirections(this.directionsRequest); 66 | } 67 | } 68 | 69 | showDirections(directionsRequest: google.maps.DirectionsRequest) { 70 | this.directionsService.route(directionsRequest, 71 | (response: any, status: any) => { 72 | // in some-case the callback is called during destroy component, 73 | // we should make sure directionsRenderer is still defined (cancelling `route` callback is not possible). 74 | if (!this.directionsRenderer) { 75 | return; 76 | } 77 | 78 | if (status === google.maps.DirectionsStatus.OK) { 79 | this.directionsRenderer.setDirections(response); 80 | } else { 81 | console.error('Directions request failed due to ' + status); 82 | } 83 | } 84 | ); 85 | } 86 | 87 | ngOnDestroy() { 88 | super.ngOnDestroy(); 89 | this.nguiMap.clearObjectEvents(this.outputs, this, 'directionsRenderer'); 90 | } 91 | } -------------------------------------------------------------------------------- /src/directives/base-map-directive.ts: -------------------------------------------------------------------------------- 1 | import {EventEmitter, SimpleChanges, Output, OnInit, OnChanges, OnDestroy} from '@angular/core'; 2 | 3 | import { OptionBuilder } from '../services/option-builder'; 4 | import { NguiMap } from '../services/ngui-map'; 5 | import { NguiMapComponent } from '../components/ngui-map.component'; 6 | import { missingLibraryError } from '../services/util'; 7 | export abstract class BaseMapDirective implements OnInit, OnChanges, OnDestroy { 8 | // this should be redefined on each childr directive 9 | @Output() initialized$: EventEmitter = new EventEmitter(); 10 | 11 | public mapObject: any; // e.g. google.maps.Marker 12 | public objectOptions: any; // e.g. google.maps.MarkerOptions 13 | 14 | public nguiMap: NguiMap; 15 | public optionBuilder: OptionBuilder; 16 | public libraryName: string; 17 | protected _subscriptions = []; 18 | 19 | constructor( 20 | protected nguiMapComponent: NguiMapComponent, 21 | public mapObjectName: string, 22 | protected inputs: string[], 23 | protected outputs: string[] 24 | ) { 25 | this.nguiMap = this.nguiMapComponent['nguiMap']; 26 | this.optionBuilder = this.nguiMapComponent['optionBuilder']; 27 | // all outputs must be initialized 28 | this.outputs.forEach(output => this[output] = new EventEmitter()); 29 | this.mapObjectName = mapObjectName; 30 | } 31 | 32 | // Initialize this map object when map is ready 33 | ngOnInit() { 34 | if (this.nguiMapComponent.mapIdledOnce) { // map is ready already 35 | this.initialize(); 36 | } else { 37 | this.nguiMapComponent.mapReady$.subscribe(map => this.initialize()); 38 | } 39 | } 40 | 41 | // only called when map is ready 42 | initialize(): void { 43 | this.objectOptions = this.optionBuilder.googlizeAllInputs(this.inputs, this); 44 | console.log(this.mapObjectName, 'initialization options', this.objectOptions); 45 | 46 | // will be set after geocoded 47 | typeof this.objectOptions.position === 'string' && (delete this.objectOptions.position); 48 | typeof this.objectOptions.center === 'string' && (delete this.objectOptions.center); 49 | 50 | // noinspection TypeScriptUnresolvedFunction 51 | if (this.libraryName) { 52 | if (!google.maps[this.libraryName]) { 53 | throw missingLibraryError(this.mapObjectName, this.libraryName); 54 | } 55 | this.mapObject = new google.maps[this.libraryName][this.mapObjectName](this.objectOptions); 56 | } else { 57 | this.mapObject = new google.maps[this.mapObjectName](this.objectOptions); 58 | } 59 | this.mapObject.setMap(this.nguiMapComponent.map); 60 | this.mapObject['mapObjectName'] = this.mapObjectName; 61 | this.mapObject['nguiMapComponent'] = this.nguiMapComponent; 62 | 63 | // set google events listeners and emits to this outputs listeners 64 | this.nguiMap.setObjectEvents(this.outputs, this, 'mapObject'); 65 | 66 | this.nguiMapComponent.addToMapObjectGroup(this.mapObjectName, this.mapObject); 67 | this.initialized$.emit(this.mapObject); 68 | } 69 | 70 | // When input is changed, update object too. 71 | // e.g., when map center is changed by user, update center on the map 72 | ngOnChanges(changes: SimpleChanges) { 73 | console.log(this.mapObjectName, 'objectOptions are changed', changes); 74 | this.nguiMap.updateGoogleObject(this.mapObject, changes); 75 | } 76 | 77 | // When destroyed, remove event listener, and delete this object to prevent memory leak 78 | ngOnDestroy() { 79 | this._subscriptions.map(subscription => subscription.unsubscribe()); 80 | this.nguiMapComponent.removeFromMapObjectGroup(this.mapObjectName, this.mapObject); 81 | 82 | if (this.mapObject) { 83 | this.nguiMap.clearObjectEvents(this.outputs, this, 'mapObject'); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/components/info-window.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | EventEmitter, 5 | SimpleChanges, 6 | ViewChild, ViewContainerRef, 7 | Output, OnInit, OnChanges, OnDestroy 8 | } from '@angular/core'; 9 | import { Subject } from 'rxjs'; 10 | import { debounceTime, tap } from 'rxjs/operators'; 11 | import { NguiMap } from '../services/ngui-map'; 12 | import { NguiMapComponent } from './ngui-map.component'; 13 | 14 | const INPUTS = [ 15 | 'content', 'disableAutoPan', 'maxWidth', 'pixelOffset', 'position', 'zIndex', 'options' 16 | ]; 17 | const OUTPUTS = [ 18 | 'closeclick', 'content_changed', 'domready', 'position_changed', 'zindex_changed' 19 | ]; 20 | 21 | @Component({ 22 | selector: 'ngui-map > info-window', 23 | inputs: INPUTS, 24 | outputs: OUTPUTS, 25 | template: `
`, 26 | }) 27 | export class InfoWindow implements OnInit, OnChanges, OnDestroy { 28 | @Output() initialized$: EventEmitter = new EventEmitter(); 29 | 30 | public infoWindow: google.maps.InfoWindow; 31 | public objectOptions: google.maps.InfoWindowOptions = {}; 32 | public inputChanges$ = new Subject(); 33 | @ViewChild('template', {read: ViewContainerRef}) template: ViewContainerRef; 34 | 35 | constructor( 36 | private elementRef: ElementRef, 37 | private nguiMap: NguiMap, 38 | private nguiMapComponent: NguiMapComponent, 39 | ) { 40 | this.elementRef.nativeElement.style.display = 'none'; 41 | OUTPUTS.forEach(output => this[output] = new EventEmitter()); 42 | } 43 | 44 | // Initialize this map object when map is ready 45 | ngOnInit() { 46 | if (this.nguiMapComponent.mapIdledOnce) { // map is ready already 47 | this.initialize(); 48 | } else { 49 | this.nguiMapComponent.mapReady$.subscribe(map => this.initialize()); 50 | } 51 | } 52 | 53 | ngOnChanges(changes: SimpleChanges) { 54 | this.inputChanges$.next(changes); 55 | } 56 | 57 | // called when map is ready 58 | initialize(): void { 59 | console.log('infowindow is being initialized'); 60 | 61 | this.objectOptions = this.nguiMapComponent.optionBuilder.googlizeAllInputs(INPUTS, this); 62 | this.infoWindow = new google.maps.InfoWindow(this.objectOptions); 63 | this.infoWindow['mapObjectName'] = 'InfoWindow'; 64 | console.log('INFOWINDOW objectOptions', this.objectOptions); 65 | 66 | // register infoWindow ids to NguiMap, so that it can be opened by id 67 | if (this.elementRef.nativeElement.id) { 68 | this.nguiMapComponent.infoWindows[this.elementRef.nativeElement.id] = this; 69 | } else { 70 | console.error('An InfoWindow must have an id. e.g. id="detail"'); 71 | } 72 | 73 | // set google events listeners and emits to this outputs listeners 74 | this.nguiMap.setObjectEvents(OUTPUTS, this, 'infoWindow'); 75 | 76 | // update object when input changes 77 | this.inputChanges$.pipe( 78 | debounceTime(1000), 79 | tap((changes: SimpleChanges) => this.nguiMap.updateGoogleObject(this.infoWindow, changes)) 80 | ).subscribe(); 81 | 82 | this.nguiMapComponent.addToMapObjectGroup('InfoWindow', this.infoWindow); 83 | this.initialized$.emit(this.infoWindow); 84 | } 85 | 86 | open(anchor: google.maps.MVCObject) { 87 | // set content and open it 88 | this.infoWindow.setContent(this.template.element.nativeElement); 89 | this.infoWindow.open(this.nguiMapComponent.map, anchor); 90 | } 91 | close() { 92 | // check if infoWindow exists, and closes it 93 | if (this.infoWindow) 94 | this.infoWindow.close(); 95 | } 96 | ngOnDestroy() { 97 | this.inputChanges$.complete(); 98 | if (this.infoWindow) { 99 | this.nguiMap.clearObjectEvents(OUTPUTS, this, 'infoWindow'); 100 | delete this.infoWindow; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "@ngui/map": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "dist/app", 15 | "index": "app/index.html", 16 | "main": "app/main.ts", 17 | "tsConfig": "app/tsconfig.app.json", 18 | "polyfills": "app/polyfills.ts", 19 | "assets": [ 20 | ], 21 | "styles": [ 22 | "app/styles.css" 23 | ], 24 | "scripts": [] 25 | }, 26 | "configurations": { 27 | "production": { 28 | "optimization": true, 29 | "outputHashing": "all", 30 | "sourceMap": false, 31 | "extractCss": true, 32 | "namedChunks": false, 33 | "aot": true, 34 | "extractLicenses": true, 35 | "vendorChunk": false, 36 | "buildOptimizer": true, 37 | "fileReplacements": [ 38 | { 39 | "replace": "app/environments/environment.ts", 40 | "with": "app/environments/environment.prod.ts" 41 | } 42 | ] 43 | } 44 | } 45 | }, 46 | "serve": { 47 | "builder": "@angular-devkit/build-angular:dev-server", 48 | "options": { 49 | "browserTarget": "@ngui/map:build" 50 | }, 51 | "configurations": { 52 | "production": { 53 | "browserTarget": "@ngui/map:build:production" 54 | } 55 | } 56 | }, 57 | "extract-i18n": { 58 | "builder": "@angular-devkit/build-angular:extract-i18n", 59 | "options": { 60 | "browserTarget": "@ngui/map:build" 61 | } 62 | }, 63 | "test": { 64 | "builder": "@angular-devkit/build-angular:karma", 65 | "options": { 66 | "main": "app/test.ts", 67 | "karmaConfig": "./karma.conf.js", 68 | "polyfills": "app/polyfills.ts", 69 | "tsConfig": "app/tsconfig.spec.json", 70 | "scripts": [], 71 | "styles": [ 72 | "app/styles.css" 73 | ], 74 | "assets": [ 75 | ] 76 | } 77 | }, 78 | "lint": { 79 | "builder": "@angular-devkit/build-angular:tslint", 80 | "options": { 81 | "tsConfig": [ 82 | "src/tsconfig.app.json", 83 | "src/tsconfig.spec.json" 84 | ], 85 | "exclude": [ 86 | "**/node_modules/**" 87 | ] 88 | } 89 | } 90 | } 91 | }, 92 | "@ngui/map-e2e": { 93 | "root": "", 94 | "sourceRoot": "", 95 | "projectType": "application", 96 | "architect": { 97 | "e2e": { 98 | "builder": "@angular-devkit/build-angular:protractor", 99 | "options": { 100 | "protractorConfig": "./protractor.conf.js", 101 | "devServerTarget": "@ngui/map:serve" 102 | } 103 | }, 104 | "lint": { 105 | "builder": "@angular-devkit/build-angular:tslint", 106 | "options": { 107 | "tsConfig": [ 108 | "e2e/tsconfig.e2e.json" 109 | ], 110 | "exclude": [ 111 | "**/node_modules/**" 112 | ] 113 | } 114 | } 115 | } 116 | } 117 | }, 118 | "defaultProject": "@ngui/map", 119 | "schematics": { 120 | "@schematics/angular:class": { 121 | "spec": false 122 | }, 123 | "@schematics/angular:component": { 124 | "prefix": "app", 125 | "styleext": "css" 126 | }, 127 | "@schematics/angular:directive": { 128 | "prefix": "app" 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /webtest.txt: -------------------------------------------------------------------------------- 1 | Ngui Map Test On Browser 2 | 3 | START 4 | open browser 5 | go to http://localhost:9239 6 | 7 | Simple Map 8 | click "li[routerlink='/simple-map']" 9 | verify script "nguiMapRef.map.getCenter()" 10 | verify script "nguiMapRef.map.getCenter().lat() === 42.99" 11 | 12 | Simple Circle 13 | click "li[routerlink='/simple-circle']" 14 | verify script "nguiMapRef.map.getCenter()" 15 | verify script "nguiMapRef.map.circles" 16 | verify script "nguiMapRef.map.circles.length == 1" 17 | 18 | Simple Marker 19 | click "li[routerlink='/simple-marker']" 20 | verify script "nguiMapRef.map.getCenter()" 21 | verify script "nguiMapRef.map.markers" 22 | verify script "nguiMapRef.map.markers.length == 1" 23 | 24 | Marker With ngFor 25 | click "li[routerlink='/marker-ng-for']" 26 | verify script "nguiMapRef.map.getCenter()" 27 | verify script "nguiMapRef.map.markers" 28 | verify script "nguiMapRef.map.markers.length == 9" 29 | click button 30 | verify script "nguiMapRef.map.markers.length == 9" 31 | 32 | Simple InfoWindow 33 | click "li[routerlink='/simple-info-window']" 34 | verify script "nguiMapRef.map.getCenter()" 35 | verify script "nguiMapRef.map.markers" 36 | run script "google.maps.event.trigger(nguiMapRef.map.markers[0], 'click')" 37 | verify script "nguiMapRef.map.infowindows" 38 | verify script "nguiMapRef.map.infowindows[0].anchor" 39 | 40 | Polygon 41 | click "li[routerlink='/polygon']" 42 | verify script "nguiMapRef.map.getCenter()" 43 | verify script "nguiMapRef.map.polygons" 44 | 45 | Map With Options 46 | click "li[routerlink='/map-with-options']" 47 | verify script "nguiMapRef.map.getCenter()" 48 | verify script "nguiMapRef.map.getZoom() === 18" 49 | verify script "nguiMapRef.map.getMapTypeId() === 'satellite'" 50 | verify script "nguiMapRef.map.getTilt() === 45" 51 | 52 | Map -- Change Multiple Properties 53 | click "li[routerlink='/map-change-multiple-properties']" 54 | verify script "nguiMapRef.map.getCenter()" 55 | verify script "nguiMapRef.map.getZoom() === 11" 56 | verify script "nguiMapRef.map.getCenter().lat() == 42.99" 57 | click button#change-props 58 | verify script "nguiMapRef.map.getZoom() === 8" 59 | verify script "nguiMapRef.map.getCenter().lat() == 40.7127753" 60 | 61 | Simple Polyline 62 | click "li[routerlink='/simple-polyline']" 63 | verify script "nguiMapRef.map.getCenter()" 64 | verify script "nguiMapRef.map.polylines" 65 | 66 | Simple Ground Overay 67 | click "li[routerlink='/simple-ground-overlay']" 68 | verify script "nguiMapRef.map.getCenter()" 69 | verify script "nguiMapRef.map.groundoverlays" 70 | 71 | Bicycling Layer 72 | click "li[routerlink='/bicycling-layer']" 73 | verify script "nguiMapRef.map.getCenter()" 74 | verify script "nguiMapRef.map.bicyclinglayers" 75 | 76 | Traffic Layer 77 | click "li[routerlink='/traffic-layer']" 78 | verify script "nguiMapRef.map.getCenter()" 79 | verify script "nguiMapRef.map.trafficlayers" 80 | 81 | Transit Layer 82 | click "li[routerlink='/transit-layer']" 83 | verify script "nguiMapRef.map.getCenter()" 84 | verify script "nguiMapRef.map.transitlayers" 85 | 86 | Heatmap Layer 87 | click "li[routerlink='/heatmap-layer']" 88 | verify script "nguiMapRef.map.getCenter()" 89 | verify script "nguiMapRef.map.heatmaplayers" 90 | 91 | Kml Layer 92 | click "li[routerlink='/kml-layer']" 93 | verify script "nguiMapRef.map.getCenter()" 94 | verify script "nguiMapRef.map.kmllayers" 95 | 96 | Data Layer 97 | click "li[routerlink='/data-layer']" 98 | verify script "nguiMapRef.map.getCenter()" 99 | verify script "nguiMapRef.map.datas 100 | 101 | Street View Panorama 102 | click "li[routerlink='/data-layer']" 103 | verify script "nguiMapRef.map.getCenter()" 104 | verify script "nguiMapRef.map.streetView" 105 | --verify script "nguiMapRef.map.streetviewpanoramas" 106 | 107 | Places Autocomplete 108 | click "li[routerlink='/places-auto-complete']" 109 | enter text "Brampton, ON" into "Enter a location" 110 | click ".pac-item:nth-child(1)" 111 | verify element "input" value is "Brampton, ON, Canada" 112 | 113 | Directions Renderer 114 | click "li[routerlink='/directions-renderer']" 115 | verify script "nguiMapRef.map.getCenter()" 116 | see 'New York, NY 10119, USA' 117 | enter text "Grand" into select 118 | see '89 E 42nd St, New York, NY 10017, USA' 119 | 120 | Drawing Manager 121 | click "li[routerlink='/drawing-manager']" 122 | verify script "nguiMapRef.map.getCenter()" 123 | verify script "nguiMapRef.map.drawingmanagers" 124 | 125 | Event Arguments 126 | click "li[routerlink='/event-arguments']" 127 | verify script "nguiMapRef.map.getCenter()" 128 | 129 | Custom Marker 130 | click "li[routerlink='/custom-marker']" 131 | verify script "nguiMapRef.map.getCenter()" 132 | verify script "nguiMapRef.map.markers" 133 | verify script "nguiMapRef.map.custommarkers" 134 | 135 | Custom Marker NgFor 136 | click "li[routerlink='/custom-marker-ng-for']" 137 | verify script "nguiMapRef.map.getCenter()" 138 | verify script "nguiMapRef.map.custommarkers" 139 | verify script "nguiMapRef.map.custommarkers.length == 9" 140 | 141 | END 142 | close browser 143 | 144 | 145 | -------------------------------------------------------------------------------- /app/app.route.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | import { ModuleWithProviders } from '@angular/core'; 3 | import { SimpleInfoWindowComponent } from './map-components/simple-info-window.component'; 4 | import { SimpleMapComponent } from './map-components/simple-map.component'; 5 | import { SimpleCircleComponent } from './map-components/simple-circle.component'; 6 | import { SimpleMarkerComponent } from './map-components/simple-marker.component'; 7 | import { MarkerNgForComponent } from './map-components/marker-ng-for.component'; 8 | import { MultipleMapComponent } from './map-components/multiple-map.component'; 9 | import { PolygonComponent } from './map-components/polygon.component'; 10 | import { MapWithOptionsComponent } from './map-components/map-with-options.component'; 11 | import { SimplePolylineComponent } from './map-components/simple-polyline.component'; 12 | import { SimpleGroundOverlayComponent } from './map-components/simple-ground-overlay.component'; 13 | import { BicyclingLayerComponent } from './map-components/bicycling-layer.component'; 14 | import { TrafficLayerComponent } from './map-components/traffic-layer.component'; 15 | import { TransitLayerComponent } from './map-components/transit-layer.component'; 16 | import { HeatmapLayerComponent } from './map-components/heatmap-layer.component'; 17 | import { KmlLayerComponent } from './map-components/kml-layer.component'; 18 | import { DataLayerComponent } from './map-components/data-layer.component'; 19 | import { StreetViewPanoramaComponent } from './map-components/street-view-panorama.component'; 20 | import { PlacesAutoCompleteComponent } from './map-components/places-auto-complete.component'; 21 | import { DirectionsRendererComponent } from './map-components/directions-renderer.component'; 22 | import { DrawingManagerComponent } from './map-components/drawing-manager.component'; 23 | import { EventArgumentsComponent } from './map-components/event-arguments.component'; 24 | import { CustomMarkerComponent } from './map-components/custom-marker.component'; 25 | import { CustomMarkerNgForComponent } from './map-components/custom-marker-ng-for.component'; 26 | import { MapWithStreetviewComponent } from './map-components/map-with-streetview.component'; 27 | import { MapChangeMultiplePropertiesComponent } from './map-components/map-change-multiple-properties.component'; 28 | import { MarkerWithCustomIconComponent } from './map-components/marker-with-custom-icon.component'; 29 | import { ExperimentComponent } from './map-components/experiment.component'; 30 | 31 | export const routes: Routes = [ 32 | { path: 'bicycling-layer', component: BicyclingLayerComponent }, 33 | { path: 'data-layer', component: DataLayerComponent }, 34 | { path: 'directions-renderer', component: DirectionsRendererComponent }, 35 | { path: 'drawing-manager', component: DrawingManagerComponent }, 36 | { path: 'heatmap-layer', component: HeatmapLayerComponent }, 37 | { path: 'kml-layer', component: KmlLayerComponent }, 38 | { path: 'map-with-options', component: MapWithOptionsComponent }, 39 | { path: 'map-with-streetview', component: MapWithStreetviewComponent }, 40 | { path: 'map-change-multiple-properties', component: MapChangeMultiplePropertiesComponent }, 41 | { path: 'marker-ng-for', component: MarkerNgForComponent }, 42 | { path: 'marker-with-custom-icon', component: MarkerWithCustomIconComponent}, 43 | { path: 'multiple-map', component: MultipleMapComponent }, 44 | { path: 'places-auto-complete', component: PlacesAutoCompleteComponent }, 45 | { path: 'polygon', component: PolygonComponent }, 46 | { path: 'simple-circle', component: SimpleCircleComponent }, 47 | { path: 'simple-ground-overlay', component: SimpleGroundOverlayComponent }, 48 | { path: 'simple-info-window', component: SimpleInfoWindowComponent }, 49 | { path: 'simple-map', component: SimpleMapComponent }, 50 | { path: 'simple-marker', component: SimpleMarkerComponent }, 51 | { path: 'simple-polyline', component: SimplePolylineComponent }, 52 | { path: 'street-view-panorama', component: StreetViewPanoramaComponent }, 53 | { path: 'traffic-layer', component: TrafficLayerComponent }, 54 | { path: 'transit-layer', component: TransitLayerComponent }, 55 | { path: 'event-arguments', component: EventArgumentsComponent }, 56 | { path: 'custom-marker', component: CustomMarkerComponent }, 57 | { path: 'custom-marker-ng-for', component: CustomMarkerNgForComponent }, 58 | { path: 'experiment', component: ExperimentComponent }, 59 | { path: '', redirectTo: '/simple-marker', pathMatch: 'full' }, 60 | ]; 61 | 62 | export const APP_ROUTER_PROVIDERS: ModuleWithProviders = RouterModule.forRoot(routes); 63 | export const APP_ROUTER_COMPONENTS = [ 64 | BicyclingLayerComponent, 65 | DataLayerComponent, 66 | DirectionsRendererComponent, 67 | DrawingManagerComponent, 68 | EventArgumentsComponent, 69 | HeatmapLayerComponent, 70 | KmlLayerComponent, 71 | MapWithOptionsComponent, 72 | MapWithStreetviewComponent, 73 | MapChangeMultiplePropertiesComponent, 74 | MarkerNgForComponent, 75 | MultipleMapComponent, 76 | PlacesAutoCompleteComponent, 77 | PolygonComponent, 78 | SimpleCircleComponent, 79 | SimpleGroundOverlayComponent, 80 | SimpleInfoWindowComponent, 81 | SimpleMapComponent, 82 | SimpleMarkerComponent, 83 | SimplePolylineComponent, 84 | StreetViewPanoramaComponent, 85 | TrafficLayerComponent, 86 | TransitLayerComponent, 87 | CustomMarkerComponent, 88 | CustomMarkerNgForComponent, 89 | MarkerWithCustomIconComponent, 90 | ExperimentComponent 91 | ]; 92 | 93 | -------------------------------------------------------------------------------- /app/source-code.service.ts: -------------------------------------------------------------------------------- 1 | import {map} from 'rxjs/operators'; 2 | import {Injectable} from '@angular/core'; 3 | import {Plunker} from 'create-plunker'; 4 | import {HttpClient} from '@angular/common/http'; 5 | 6 | 7 | @Injectable() 8 | export class SourceCodeService { 9 | 10 | constructor(private http: HttpClient) { } 11 | 12 | getText(klassName: string) { 13 | let urlPrefix = 'https://raw.githubusercontent.com/ng2-ui/map/master/app/map-components'; 14 | let fileName = klassName. 15 | replace('Directive', '.directive.ts'). 16 | replace('Service', '.service.ts'). 17 | replace('Component', '.component.ts'). 18 | replace(/([A-Z])/g, (_, $1) => `-${$1.toLowerCase()}`). 19 | replace(/^-/, ''); 20 | let url = `${urlPrefix}/${fileName}`; 21 | 22 | return this.http.get(url, { responseType: 'text'}).pipe( 23 | map((res) => appComponentTsCode(res))); 24 | } 25 | 26 | plnkr(code: string) { 27 | Plunker.create() 28 | .setDescription('Angular2+ ng2-ui map demo') 29 | .addIndexHeadLine(`Ngui Map`) 30 | .addIndexHeadLine(``) 31 | .addIndexHeadLine(``) 32 | .addIndexHeadLine(``) 33 | .addIndexHeadLine(``) 34 | .addIndexHeadLine(``) 35 | .addIndexHeadLine(``) 36 | .addFile({name: 'app.component.ts', contents: appComponentTsCode(code)}) 37 | .addFile({name: 'main.ts', contents: mainTsCode()}) 38 | .addFile({name: 'systemjs.config.js', contents: systemjsConfigJsCode()}) 39 | .addFile({name: 'tsconfig.json', contents: tsconfigJsonCode()}) 40 | .addIndexHeadLine(``) 41 | .setIndexBody(`Loading...`) 42 | .save(); 43 | } 44 | } 45 | 46 | function tsconfigJsonCode() { 47 | return ` 48 | { 49 | "compilerOptions": { 50 | "target": "es5", 51 | "module": "commonjs", 52 | "moduleResolution": "node", 53 | "sourceMap": true, 54 | "emitDecoratorMetadata": true, 55 | "experimentalDecorators": true, 56 | "removeComments": false, 57 | "noImplicitAny": true, 58 | "suppressImplicitAnyIndexErrors": true 59 | } 60 | } 61 | `; 62 | } 63 | 64 | function mainTsCode() { 65 | return ` 66 | // The browser platform with a compiler 67 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 68 | 69 | import { NgModule } from '@angular/core'; 70 | import { BrowserModule } from '@angular/platform-browser'; 71 | import { FormsModule, ReactiveFormsModule } from "@angular/forms"; 72 | 73 | import { AppComponent } from './app.component'; 74 | 75 | //noinspection TypeScriptCheckImport 76 | import { NguiMapModule } from '@ngui/map'; 77 | 78 | @NgModule({ 79 | imports: [ 80 | BrowserModule, 81 | FormsModule, 82 | ReactiveFormsModule, 83 | NguiMapModule.forRoot({ 84 | apiUrl: 'https://maps.google.com/maps/api/js?libraries=visualization,places,drawing' 85 | }) 86 | ], 87 | declarations: [AppComponent], 88 | bootstrap: [ AppComponent ] 89 | }) 90 | export class AppModule { } 91 | 92 | // Compile and launch the module 93 | platformBrowserDynamic().bootstrapModule(AppModule); 94 | `; 95 | } 96 | 97 | function appComponentTsCode(code) { 98 | return code 99 | .replace(`@Component({`, `@Component({\n selector: 'my-app',`) 100 | .replace(`\nimport { SourceCodeService } from '../source-code.service';`, '') 101 | .replace(`
{{code}}
`, '') 102 | .replace(/sc\.getText\(['"A-Za-z0-9]+\)\.subscribe\(text => this\.code = text\);/, '') 103 | .replace(/[, public]*sc: SourceCodeService\)/, ')') 104 | .replace(/[\s\S]*<\/code>/, '') 105 | .replace(`code: string;`, '') 106 | .replace(/constructor\s*\(\)\s*{\s*}/m, '') 107 | .replace(``, '') 108 | .replace(/export class [A-Za-z0-9]+Component/, 'export class AppComponent') 109 | .replace(/^\s*\n/gm, '\n'); 110 | } 111 | 112 | function systemjsConfigJsCode() { 113 | return ` 114 | System.config({ 115 | // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER 116 | transpiler: 'ts', 117 | typescriptOptions: { 118 | tsconfig: true 119 | }, 120 | meta: { 121 | 'typescript': { 122 | "exports": "ts" 123 | } 124 | }, 125 | paths: { // paths serve as alias 126 | 'npm:': 'https://unpkg.com/' 127 | }, 128 | map: { // map tells the System loader where to look for things 129 | app: '.', // our app is within the app folder 130 | 131 | // angular bundles 132 | '@angular/core': 'npm:@angular/core/bundles/core.umd.js', 133 | '@angular/common': 'npm:@angular/common/bundles/common.umd.js', 134 | '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', 135 | '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', 136 | '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', 137 | '@angular/http': 'npm:@angular/http/bundles/http.umd.js', 138 | '@angular/router': 'npm:@angular/router/bundles/router.umd.js', 139 | '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', 140 | '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js', 141 | 142 | // other libraries 143 | 'rxjs': 'npm:rxjs', 144 | 'ts': 'npm:plugin-typescript@4.0.10/lib/plugin.js', 145 | 'typescript': 'npm:typescript@2.0.2/lib/typescript.js', 146 | 147 | '@ngui/map': 'npm:@ngui/map/dist/bundles/map.umd.js' 148 | }, 149 | packages: { // packages tells the System loader how to load when no filename and/or no extension 150 | app: { main: './main.ts', defaultExtension: 'ts' }, 151 | rxjs: { defaultExtension: 'js' } 152 | } 153 | }); 154 | `; 155 | } 156 | -------------------------------------------------------------------------------- /src/components/custom-marker.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | Output, 5 | EventEmitter, 6 | SimpleChanges, OnInit, OnDestroy, OnChanges, 7 | } from '@angular/core'; 8 | import { Subject } from 'rxjs'; 9 | import { debounceTime, tap } from 'rxjs/operators'; 10 | 11 | import { NguiMap } from '../services/ngui-map'; 12 | import { NguiMapComponent } from './ngui-map.component'; 13 | 14 | const INPUTS = [ 15 | 'position' 16 | ]; 17 | // to avoid DOM event conflicts map_* 18 | const OUTPUTS = [ 19 | 'animationChanged', 'click', 'clickableChanged', 'cursorChanged', 'dblclick', 'drag', 'dragend', 'draggableChanged', 20 | 'dragstart', 'flatChanged', 'iconChanged', 'mousedown', 'mouseout', 'mouseover', 'mouseup', 'positionChanged', 'rightclick', 21 | 'shapeChanged', 'titleChanged', 'visibleChanged', 'zindexChanged', 22 | 'map_click', 'map_mouseover', 'map_mouseout', 'map_mouseup', 'map_mousedown', 'map_drag', 'map_dragend' 23 | ]; 24 | 25 | /** 26 | * Wrapper to a create extend OverlayView at runtime, only after google maps is loaded. 27 | * Otherwise throws a google is unknown error. 28 | */ 29 | function getCustomMarkerOverlayView(htmlEl: HTMLElement, position: any) { 30 | 31 | class CustomMarkerOverlayView extends google.maps.OverlayView { 32 | 33 | private htmlEl: HTMLElement; 34 | private position: any; 35 | private zIndex: string; 36 | private visible: boolean = true; 37 | 38 | constructor(htmlEl: HTMLElement, position: any) { 39 | super(); 40 | this.htmlEl = htmlEl; 41 | this.position = position; 42 | } 43 | 44 | onAdd(): void { 45 | this.getPanes().overlayMouseTarget.appendChild(this.htmlEl); 46 | 47 | // required for correct display inside google maps container 48 | this.htmlEl.style.position = 'absolute'; 49 | } 50 | 51 | draw(): void { 52 | this.setPosition(this.position); 53 | this.setZIndex(this.zIndex); 54 | this.setVisible(this.visible); 55 | } 56 | 57 | onRemove(): void { 58 | // 59 | } 60 | 61 | getPosition() { 62 | return this.position; 63 | } 64 | 65 | setPosition = (position?: any) => { 66 | this.htmlEl.style.visibility = 'hidden'; 67 | 68 | if (position.constructor.name === 'Array') { 69 | this.position = new google.maps.LatLng(position[0], position[1]); 70 | } else if (typeof position === 'string') { 71 | let geocoder = new google.maps.Geocoder(); 72 | 73 | geocoder.geocode({address: position}, (results, status) => { 74 | if (status === google.maps.GeocoderStatus.OK) { 75 | console.log('setting custom marker position from address', position); 76 | this.setPosition(results[0].geometry.location); 77 | } else { 78 | console.log('Error in custom marker geo coding, position'); 79 | } 80 | }); 81 | } else if (position && typeof position.lng === 'function') { 82 | this.position = position; 83 | } 84 | 85 | if (this.getProjection() && typeof this.position.lng === 'function') { 86 | let positionOnMap = () => { 87 | let projection = this.getProjection(); 88 | if (!projection) { 89 | return; 90 | } 91 | let posPixel = projection.fromLatLngToDivPixel(this.position); 92 | let x = Math.round(posPixel.x - (this.htmlEl.offsetWidth / 2)); 93 | let y = Math.round(posPixel.y - this.htmlEl.offsetHeight / 2); 94 | this.htmlEl.style.left = x + 'px'; 95 | this.htmlEl.style.top = y + 'px'; 96 | this.htmlEl.style.visibility = 'visible'; 97 | }; 98 | 99 | if (this.htmlEl.offsetWidth && this.htmlEl.offsetHeight) { 100 | positionOnMap(); 101 | } else { 102 | setTimeout(() => positionOnMap()); 103 | } 104 | } 105 | } 106 | 107 | setZIndex(zIndex: string): void { 108 | zIndex && (this.zIndex = zIndex); /* jshint ignore:line */ 109 | this.htmlEl.style.zIndex = this.zIndex; 110 | } 111 | 112 | setVisible(visible: boolean) { 113 | this.htmlEl.style.display = visible ? 'inline-block' : 'none'; 114 | this.visible = visible; 115 | } 116 | } 117 | 118 | return new CustomMarkerOverlayView(htmlEl, position); 119 | } 120 | 121 | @Component({ 122 | selector: 'ngui-map > custom-marker', 123 | inputs: INPUTS, 124 | outputs: OUTPUTS, 125 | template: ` 126 | 127 | `, 128 | }) 129 | 130 | export class CustomMarker implements OnInit, OnDestroy, OnChanges { 131 | @Output() initialized$: EventEmitter = new EventEmitter(); 132 | 133 | public inputChanges$ = new Subject(); 134 | 135 | private el: HTMLElement; 136 | private mapObject: any; 137 | 138 | constructor(private nguiMapComponent: NguiMapComponent, 139 | private elementRef: ElementRef, 140 | private nguiMap: NguiMap) { 141 | this.elementRef.nativeElement.style.display = 'none'; 142 | OUTPUTS.forEach(output => this[output] = new EventEmitter()); 143 | } 144 | 145 | // Initialize this map object when map is ready 146 | ngOnInit() { 147 | if (this.nguiMapComponent.mapIdledOnce) { // map is ready already 148 | this.initialize(); 149 | } else { 150 | this.nguiMapComponent.mapReady$.subscribe(map => this.initialize()); 151 | } 152 | } 153 | 154 | ngOnChanges(changes: SimpleChanges) { 155 | this.inputChanges$.next(changes); 156 | } 157 | 158 | ngOnDestroy() { 159 | this.inputChanges$.complete(); 160 | this.nguiMapComponent.removeFromMapObjectGroup('CustomMarker', this.mapObject); 161 | 162 | if (this.mapObject) { 163 | this.nguiMap.clearObjectEvents(OUTPUTS, this, 'mapObject'); 164 | } 165 | } 166 | 167 | private initialize(): void { 168 | console.log('custom-marker is being initialized'); 169 | this.el = this.elementRef.nativeElement; 170 | 171 | this.mapObject = getCustomMarkerOverlayView(this.el, this['position']); 172 | this.mapObject.setMap(this.nguiMapComponent.map); 173 | 174 | // set google events listeners and emits to this outputs listeners 175 | this.nguiMap.setObjectEvents(OUTPUTS, this, 'mapObject'); 176 | 177 | // update object when input changes 178 | this.inputChanges$.pipe( 179 | debounceTime(1000), 180 | tap((changes: SimpleChanges) => this.nguiMap.updateGoogleObject(this.mapObject, changes)), 181 | ).subscribe(); 182 | 183 | this.nguiMapComponent.addToMapObjectGroup('CustomMarker', this.mapObject); 184 | this.initialized$.emit(this.mapObject); 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/components/ngui-map.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | ViewEncapsulation, 5 | EventEmitter, 6 | SimpleChanges, 7 | Output, 8 | NgZone, 9 | AfterViewInit, AfterViewChecked, OnChanges, OnDestroy 10 | } from '@angular/core'; 11 | 12 | import { OptionBuilder } from '../services/option-builder'; 13 | import { NavigatorGeolocation } from '../services/navigator-geolocation'; 14 | import { GeoCoder } from '../services/geo-coder'; 15 | import { NguiMap } from '../services/ngui-map'; 16 | import { NgMapApiLoader } from '../services/api-loader'; 17 | import { InfoWindow } from './info-window'; 18 | import { Subject } from 'rxjs'; 19 | import { debounceTime, tap, first } from 'rxjs/operators'; 20 | import { toCamelCase } from '../services/util'; 21 | 22 | const INPUTS = [ 23 | 'backgroundColor', 'center', 'disableDefaultUI', 'disableDoubleClickZoom', 'draggable', 'draggableCursor', 24 | 'draggingCursor', 'heading', 'keyboardShortcuts', 'mapMaker', 'mapTypeControl', 'mapTypeId', 'maxZoom', 'minZoom', 25 | 'noClear', 'overviewMapControl', 'panControl', 'panControlOptions', 'rotateControl', 'scaleControl', 'scrollwheel', 26 | 'streetView', 'styles', 'tilt', 'zoom', 'streetViewControl', 'zoomControl', 'zoomControlOptions', 'mapTypeControlOptions', 27 | 'overviewMapControlOptions', 'rotateControlOptions', 'scaleControlOptions', 'streetViewControlOptions', 'fullscreenControl', 'fullscreenControlOptions', 28 | 'options', 29 | // ngui-map-specific inputs 30 | 'geoFallbackCenter' 31 | ]; 32 | 33 | const OUTPUTS = [ 34 | 'bounds_changed', 'center_changed', 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'heading_changed', 'idle', 35 | 'maptypeid_changed', 'mousemove', 'mouseout', 'mouseover', 'projection_changed', 'resize', 'rightclick', 36 | 'tilesloaded', 'tile_changed', 'zoom_changed', 37 | // to avoid DOM event conflicts 38 | 'mapClick', 'mapMouseover', 'mapMouseout', 'mapMousemove', 'mapDrag', 'mapDragend', 'mapDragstart' 39 | ]; 40 | 41 | @Component({ 42 | selector: 'ngui-map', 43 | providers: [NguiMap, OptionBuilder, GeoCoder, NavigatorGeolocation], 44 | styles: [` 45 | ngui-map {display: block; height: 300px;} 46 | .google-map {width: 100%; height: 100%} 47 | `], 48 | inputs: INPUTS, 49 | outputs: OUTPUTS, 50 | encapsulation: ViewEncapsulation.None, 51 | template: ` 52 |
53 | 54 | `, 55 | }) 56 | export class NguiMapComponent implements OnChanges, OnDestroy, AfterViewInit, AfterViewChecked { 57 | @Output() public mapReady$: EventEmitter = new EventEmitter(); 58 | 59 | public el: HTMLElement; 60 | public map: google.maps.Map; 61 | public mapOptions: google.maps.MapOptions = {}; 62 | 63 | public inputChanges$ = new Subject(); 64 | 65 | // map objects by group 66 | public infoWindows: { [id: string]: InfoWindow } = { }; 67 | 68 | // map has been fully initialized 69 | public mapIdledOnce: boolean = false; 70 | 71 | private initializeMapAfterDisplayed = false; 72 | private apiLoaderSub; 73 | 74 | constructor( 75 | public optionBuilder: OptionBuilder, 76 | public elementRef: ElementRef, 77 | public geolocation: NavigatorGeolocation, 78 | public geoCoder: GeoCoder, 79 | public nguiMap: NguiMap, 80 | public apiLoader: NgMapApiLoader, 81 | public zone: NgZone, 82 | ) { 83 | apiLoader.load(); 84 | 85 | // all outputs needs to be initialized, 86 | // http://stackoverflow.com/questions/37765519/angular2-directive-cannot-read-property-subscribe-of-undefined-with-outputs 87 | OUTPUTS.forEach(output => this[output] = new EventEmitter()); 88 | } 89 | 90 | ngAfterViewInit() { 91 | this.apiLoaderSub = this.apiLoader.api$ 92 | .pipe(first()) 93 | .subscribe(() => this.initializeMap()); 94 | } 95 | 96 | ngAfterViewChecked() { 97 | if (this.initializeMapAfterDisplayed && this.el && this.el.offsetWidth > 0) { 98 | this.initializeMap(); 99 | } 100 | } 101 | 102 | ngOnChanges(changes: SimpleChanges) { 103 | this.inputChanges$.next(changes); 104 | } 105 | 106 | initializeMap(): void { 107 | this.el = this.elementRef.nativeElement.querySelector('.google-map'); 108 | if (this.el && this.el.offsetWidth === 0) { 109 | this.initializeMapAfterDisplayed = true; 110 | return; 111 | } 112 | 113 | this.initializeMapAfterDisplayed = false; 114 | this.mapOptions = this.optionBuilder.googlizeAllInputs(INPUTS, this); 115 | console.log('ngui-map mapOptions', this.mapOptions); 116 | 117 | this.mapOptions.zoom = this.mapOptions.zoom || 15; 118 | typeof this.mapOptions.center === 'string' && (delete this.mapOptions.center); 119 | 120 | this.zone.runOutsideAngular(() => { 121 | this.map = new google.maps.Map(this.el, this.mapOptions); 122 | this.map['mapObjectName'] = 'NguiMapComponent'; 123 | 124 | if (!this.mapOptions.center) { // if center is not given as lat/lng 125 | this.setCenter(); 126 | } 127 | 128 | // set google events listeners and emits to this outputs listeners 129 | this.nguiMap.setObjectEvents(OUTPUTS, this, 'map'); 130 | 131 | this.map.addListener('idle', () => { 132 | if (!this.mapIdledOnce) { 133 | this.mapIdledOnce = true; 134 | setTimeout(() => { // Why????, subsribe and emit must not be in the same cycle??? 135 | this.mapReady$.emit(this.map); 136 | }); 137 | } 138 | }); 139 | 140 | // update map when input changes 141 | this.inputChanges$.pipe( 142 | debounceTime(1000), 143 | tap((changes: SimpleChanges) => this.nguiMap.updateGoogleObject(this.map, changes)), 144 | ).subscribe(); 145 | 146 | if (typeof window !== 'undefined' && (window)['nguiMapRef']) { 147 | // expose map object for test and debugging on (window) 148 | (window)['nguiMapRef'].map = this.map; 149 | } 150 | }); 151 | } 152 | 153 | setCenter(): void { 154 | if (!this['center']) { // center is not from user. Thus, we set the current location 155 | this.geolocation.getCurrentPosition().subscribe( 156 | position => { 157 | console.log('setting map center from current location'); 158 | let latLng = new google.maps.LatLng(position.coords.latitude, position.coords.longitude); 159 | this.map.setCenter(latLng); 160 | }, 161 | error => { 162 | console.error('ngui-map: Error finding the current position'); 163 | this.map.setCenter(this.mapOptions['geoFallbackCenter'] || new google.maps.LatLng(0, 0)); 164 | } 165 | ); 166 | } 167 | else if (typeof this['center'] === 'string') { 168 | this.geoCoder.geocode({address: this['center']}).subscribe( 169 | results => { 170 | console.log('setting map center from address', this['center']); 171 | this.map.setCenter(results[0].geometry.location); 172 | }, 173 | error => { 174 | this.map.setCenter(this.mapOptions['geoFallbackCenter'] || new google.maps.LatLng(0, 0)); 175 | }); 176 | } 177 | } 178 | 179 | openInfoWindow(id: string, anchor: google.maps.MVCObject) { 180 | this.infoWindows[id].open(anchor); 181 | } 182 | 183 | closeInfoWindow(id: string) { 184 | // if infoWindow for id exists, close the infoWindow 185 | if (this.infoWindows[id]) 186 | this.infoWindows[id].close(); 187 | } 188 | 189 | ngOnDestroy() { 190 | this.inputChanges$.complete(); 191 | if (this.el && !this.initializeMapAfterDisplayed) { 192 | this.nguiMap.clearObjectEvents(OUTPUTS, this, 'map'); 193 | } 194 | if (this.apiLoaderSub) { 195 | this.apiLoaderSub.unsubscribe(); 196 | } 197 | } 198 | 199 | // map.markers, map.circles, map.heatmapLayers.. etc 200 | addToMapObjectGroup(mapObjectName: string, mapObject: any) { 201 | let groupName = toCamelCase(mapObjectName.toLowerCase()) + 's'; // e.g. markers 202 | this.map[groupName] = this.map[groupName] || []; 203 | this.map[groupName].push(mapObject); 204 | } 205 | 206 | removeFromMapObjectGroup(mapObjectName: string, mapObject: any) { 207 | let groupName = toCamelCase(mapObjectName.toLowerCase()) + 's'; // e.g. markers 208 | if (this.map && this.map[groupName]) { 209 | let index = this.map[groupName].indexOf(mapObject); 210 | console.log('index', mapObject, index); 211 | (index > -1) && this.map[groupName].splice(index, 1); 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/services/option-builder.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { getJSON, IJson } from './util'; 3 | 4 | /** 5 | * change any object to google object options 6 | * e.g. [1,2] -> new google.maps.LatLng(1,2); 7 | */ 8 | @Injectable() 9 | export class OptionBuilder { 10 | 11 | googlizeAllInputs(definedInputs: string[], userInputs: any) { 12 | let options: any = {}; 13 | 14 | // if options given from user, only take options and ignore other inputs 15 | if (userInputs.options) { 16 | console.log('userInputs.options .................', userInputs.options); 17 | options = userInputs.options; 18 | if (!this.onlyOptionsGiven(definedInputs, userInputs)) { 19 | console.error('when "options" are used, other options are ignored'); 20 | } 21 | } else { // if options not given, process all user inputs 22 | definedInputs.forEach(input => { 23 | if (userInputs[input] !== undefined) { 24 | options[input] = this.googlize(userInputs[input], {key: input}); 25 | } 26 | }); 27 | } 28 | return options; 29 | } 30 | 31 | googlizeMultiple(inputs: any[], options?: IJson): any { 32 | options = options || {}; 33 | for (let key in inputs) { 34 | let val = inputs[key]; 35 | // (non-strings are fully converted) 36 | if (typeof val !== 'string') { 37 | options[key] = val; 38 | } // sometimes '0' needed to stay as it is 39 | else if (!(options['doNotConverStringToNumber'] && val.match(/^[0-9]+$/))) { 40 | options[key] = this.googlize(val, {key: key}); 41 | } 42 | } // for(var key in attrs) 43 | return options; 44 | } 45 | 46 | googlize(input: any, options?: IJson): any { 47 | options = options || {}; 48 | let output: any = input; 49 | if (typeof input === 'string') { // convert string to a google object 50 | if (input === 'false') { 51 | output = false; 52 | } else if (input === '0') { 53 | output = 0; 54 | } else { 55 | output = 56 | // -> googlize -> getJsonParsed -> googlizeMultiple -> googlize until all elements are parsed 57 | this.getJSONParsed(input, options) 58 | 59 | /* Foo.Bar(...) -> new google.maps.Foo.Bar(...) */ 60 | || this.getAnyMapObject(input) 61 | 62 | /* MapTypeID.HYBRID -> new google.maps.MapTypeID.HYBRID */ 63 | || this.getAnyMapConstant(input, options) 64 | 65 | /* 2016-06-20 -> new Date('2016-06-20') */ 66 | || this.getDateObject(input) 67 | 68 | || input; 69 | } 70 | } 71 | 72 | if (options['key']) { 73 | let key: string = options['key']; 74 | if (output instanceof Array) { // e.g., [1, 2] 75 | if (key === 'bounds') { 76 | output = new google.maps.LatLngBounds(output[0], output[1]); 77 | } else if (key === 'icons') { 78 | output = this.getMapIcons(output); 79 | } else if (key === 'position' || key.match(/^geoFallback/) ) { 80 | output = this.getLatLng(output); 81 | } 82 | } else if (output instanceof Object) { 83 | if (key === 'icon') { 84 | output = this.getMarkerIcon(output); 85 | } 86 | else if (key.match(/ControlOptions$/)) { 87 | output = this.getMapControlOption(output); 88 | } 89 | } 90 | } 91 | 92 | // delete keys only for processing, not used by google 93 | delete output['doNotConverStringToNumber']; 94 | delete output['key']; 95 | 96 | return output; 97 | } 98 | 99 | private getLatLng(input: any): google.maps.LatLng | Array { 100 | let output: google.maps.LatLng | Array; 101 | if (input[0].constructor === Array) { // [[1,2],[3,4]] 102 | output = (input).map((el: number[]) => new google.maps.LatLng(el[0], el[1])); 103 | } else if (!isNaN(parseFloat(input[0])) && isFinite(input[0])) { 104 | output = new google.maps.LatLng(input[0], input[1]); 105 | } 106 | return output; 107 | } 108 | 109 | private getJSONParsed(input: any, options: IJson): IJson { 110 | let output: any; 111 | try { 112 | output = getJSON(input); 113 | if (output instanceof Array) { 114 | // [{a:1}] : not lat/lng ones 115 | 116 | if (output[0].constructor !== Object) { // [[1,2],[3,4]] or [1,2] 117 | output = this.getLatLng(output); 118 | } 119 | } 120 | // JSON is an object (not array or null) 121 | else if (output === Object(output)) { 122 | // check for nested hashes and convert to Google API options 123 | let newOptions = options; 124 | newOptions['doNotConverStringToNumber'] = true; 125 | output = this.googlizeMultiple(output, newOptions); 126 | } 127 | } catch (e) { 128 | } 129 | return output; 130 | } 131 | 132 | private getAnyMapObject(input: string): any { 133 | let output: any; 134 | if (input.match(/^[A-Z][a-zA-Z0-9]+\(.*\)$/)) { 135 | try { 136 | output = Function(`return new google.maps.${input};`)(); 137 | } catch (e) {} 138 | } 139 | return output; 140 | } 141 | 142 | private getAnyMapConstant(input: string, options: IJson): any { 143 | let output: any; 144 | 145 | if (input.match(/^([A-Z][a-zA-Z0-9]+)\.([A-Z]+)$/)) { // e.g. MapTypeID.HYBRID 146 | try { 147 | let matches = input.match(/^([A-Z][a-zA-Z0-9]+)\.([A-Z]+)$/); 148 | output = google.maps[matches[1]][matches[2]]; 149 | } catch (e) {} 150 | } else if (input.match(/^[A-Z]+$/)) { // e.g. HYBRID 151 | try { 152 | let capitalizedKey = (options['key']).charAt(0).toUpperCase() + 153 | (options['key']).slice(1); 154 | output = google.maps[capitalizedKey][input]; 155 | } catch (e) {} 156 | } 157 | return output; 158 | } 159 | 160 | /** 161 | * streetviewControl, panControl, etc, not a general control 162 | */ 163 | private getMapControlOption(controlOptions: IJson): IJson { 164 | let newControlOptions: IJson = controlOptions; 165 | 166 | for (let key in newControlOptions) { // assign the right values 167 | if (newControlOptions[key]) { 168 | let value = newControlOptions[key]; 169 | 170 | if (typeof value === 'string') { 171 | value = (value).toUpperCase(); 172 | } 173 | else if (key === 'mapTypeIds') { 174 | value = (value).map(function (str) { 175 | if (str.match(/^[A-Z]+$/)) { // if constant 176 | return google.maps.MapTypeId[str.toUpperCase()]; 177 | } else { // else, custom map-type 178 | return str; 179 | } 180 | }); 181 | } 182 | 183 | if (key === 'style') { 184 | let objName = key.replace(/Options$/, '') + 'Style'; 185 | newControlOptions[key] = google.maps[objName][value]; 186 | } 187 | else if (key === 'position') { 188 | newControlOptions[key] = google.maps.ControlPosition[value]; 189 | } 190 | else { 191 | newControlOptions[key] = value; 192 | } 193 | } 194 | } 195 | 196 | return newControlOptions; 197 | } 198 | 199 | private getDateObject(input: string): Date { 200 | let output: Date; 201 | 202 | if (input.match(/^(\d{4}\-\d\d\-\d\d([tT][\d:\.]*)?)([zZ]|([+\-])(\d\d):?(\d\d))?$/)) { 203 | try { 204 | output = new Date(input); 205 | } catch (e) {} 206 | } 207 | return output; 208 | } 209 | 210 | private getMapIcons(input: any[]): any[] { 211 | return input.map(el => { 212 | if (el.icon.path.match(/^[A-Z_]+$/)) { 213 | el.icon.path = google.maps.SymbolPath[el.icon.path]; 214 | } 215 | return el; 216 | }); 217 | } 218 | 219 | private getMarkerIcon(input: any): any { 220 | let output = input; 221 | 222 | if (('' + output.path).match(/^[A-Z_]+$/)) { 223 | output.path = google.maps.SymbolPath[output.path]; 224 | } 225 | 226 | for (let key in output) { 227 | let arr = output[key]; 228 | if (key === 'anchor' || key === 'origin' || key === 'labelOrigin') { 229 | output[key] = new google.maps.Point(arr[0], arr[1]); 230 | } else if (key === 'size' || key === 'scaledSize') { 231 | output[key] = new google.maps.Size(arr[0], arr[1]); 232 | } 233 | } 234 | 235 | return output; 236 | } 237 | 238 | private onlyOptionsGiven(definedInputs: string[], userInputs: any): boolean { 239 | for (let i = 0; i < definedInputs.length; i++) { 240 | let input = definedInputs[i]; 241 | if (input !== 'options' && typeof userInputs[input] !== 'undefined') { 242 | return false; 243 | } 244 | } 245 | return true; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # map 2 | 3 | [![Build Status](https://travis-ci.org/ng2-ui/map.svg?branch=master)](https://travis-ci.org/ng2-ui/map) 4 | [![Join the chat at https://gitter.im/ng2-ui/map](https://badges.gitter.im/ng2-ui/map.svg)](https://gitter.im/ng2-ui/map?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Angular2 Google Map ([ng-map](https://ngmap.github.io) version 2) 7 | 8 | * **[![Imgur](http://i.imgur.com/O2EOCxf.png)](https://ng2-ui.github.io/map/)** 9 | * [Plunker Example](https://plnkr.co/edit/6e1qWK?p=preview) 10 | * [Place Auto Complete Plunker Example](https://plnkr.co/edit/AT3fxW?p=preview) 11 | 12 | If you like this, you may also like these; 13 | * [ng-map](https://github.com/allenhwkim/angularjs-google-maps) Google Maps Wrapper for Angular 1.* 14 | * [react-openlayers](https://github.com/allenhwkim/react-openlayers) React + OpenLayers 15 | * [geo-coder](https://github.com/allenhwkim/geocoder) Google/Bing/OpenStreetMap Geocoding/autocomplete/reverse lookup 16 | 17 | ### Design Principle 18 | 19 | 1. **All google properties must be able to be defined in html without Javascript.** 20 | 21 | Thus, basic users don't even have to know what Javascript is. 22 | 23 | 2. **Expose all original Google Maps V3 api to the user without any exception.** 24 | 25 | No hiding or manipulation. By doing so, programmers don't need to learn anything about this convenient module. 26 | If you know Google Maps V3 API, there shouldn't be a problem using this module. 27 | 28 | ### Usage 29 | 30 | 1. Install node_module `@ngui/map` and typings 31 | 32 | $ npm install @ngui/map @types/googlemaps --save 33 | 34 | 2. _For SystemJs users only_, update `system.config.js` to recognize @ngui/map. 35 | 36 | map['@ngui/map'] = 'node_modules/@ngui/map/dist/map.umd.js'; 37 | 38 | 3. import NguiMapModule to your AppModule 39 | 40 | import { NgModule } from '@angular/core'; 41 | import { FormsModule } from "@angular/forms"; 42 | import { BrowserModule } from '@angular/platform-browser'; 43 | 44 | import { AppComponent } from './app.component'; 45 | import { NguiMapModule} from '@ngui/map'; 46 | 47 | @NgModule({ 48 | imports: [ 49 | BrowserModule, 50 | FormsModule, 51 | NguiMapModule.forRoot({apiUrl: 'https://maps.google.com/maps/api/js?key=MY_GOOGLE_API_KEY'}) 52 | ], 53 | declarations: [AppComponent], 54 | bootstrap: [ AppComponent ] 55 | }) 56 | export class AppModule { } 57 | 58 | 59 | ## Use it in your template 60 | 61 | ``` 62 | 63 | ``` 64 | or, 65 | ``` 66 | 67 | ``` 68 | 69 | For a full example, please check out the `app` directory to see the example: 70 | 71 | - `main.ts` 72 | - and `app/map-components`. 73 | 74 | ## How to get a instance(s) of a map or markers 75 | 76 | * Nui2MapComponent fires `mapReady$` event with `map` object 77 | * Each ngui-map directives fires `initialized$` event with its Google map object, e.g. google.maps.Marker 78 | * Other way is to get a map object is to any event. All event has `target` value, which is a Google map object. 79 | 80 | ```HTML 81 | 88 | 91 | 92 | ``` 93 | 94 | In your app component: 95 | 96 | ```TypeScript 97 | export class MyAppComponent { 98 | onMapReady(map) { 99 | console.log('map', map); 100 | console.log('markers', map.markers); // to get all markers as an array 101 | } 102 | onIdle(event) { 103 | console.log('map', event.target); 104 | } 105 | onMarkerInit(marker) { 106 | console.log('marker', marker); 107 | } 108 | onMapClick(event) { 109 | this.positions.push(event.latLng); 110 | event.target.panTo(event.latLng); 111 | } 112 | } 113 | ``` 114 | 115 | ## Need Contributors 116 | 117 | This `ngui-map` module is only improved and maintained by volunteers like you; 118 | 119 | As a volunteer, it's NOT required to be skilled in Javascript or Angular2. 120 | It’s required to be open-minded and interested in helping others. 121 | You can contribute to the following; 122 | 123 | * Updating README.md 124 | * Adding clear and descriptive comments 125 | * Answering issues and building FAQ 126 | * Documentation 127 | * Translation 128 | 129 | As a result of your active contribution, you will be listed as a core contributor 130 | on https://ng2-ui.github.io, and a member of ng2-ui too. 131 | 132 | If you are interested in becoming a contributor and/or a member of ng-ui, 133 | please send me email to `allenhwkim AT gmail.com` with your github id. 134 | 135 | ### Google Maps V3 Compatibility Table 136 | 137 | 138 | 139 |
Object Options Events Note
Map 140 | MapOptions 141 | Map Events 142 | Google Simple Map Example
143 | ngui-map example 144 |
Marker 145 | MarkerOptions 146 | Marker Events 147 | Google Simple Marker Example
148 | ngui-map marker example 149 |
InfoWindow 150 | InfoWindowOptions 151 | InfoWindow Events 152 | Google Infowindows Example
153 | ngui-map info-window example 154 |
Circle 155 | CircleOptions 156 | Circle Events 157 | Google Circle example
158 | ngui-map circle example 159 |
Polygon 160 | PolygonOptions 161 | Polygon Events 162 | Google Polygon example
163 | ngui-map polygon example 164 |
Polyline 165 | PolylineOptions 166 | Polyline Events 167 | Google Polyline Example
168 | ngui-map polyline example 169 |
GroundOverlay 170 | GroundOverlayOptions 171 | GroundOverlay Events 172 | Google Simple Ground Overlay Example
173 | ngui-map ground-overlay example 174 |
FusionTablesLayer FusionTablesLayerOptions FusionTablesLayer Events Experimental Status - No Plan to implement 175 |
HeatmapLayer 176 | HeatmapLayerOptions 177 | HeatmapLayer Events 178 | Google Heatmap Layer
179 | ngui-map heatmap-layer example 180 |
KmlLayer 181 | KmlLayerOptions 182 | KmlLayer Events 183 | Google Kml Layer
184 | ngui-map kml-layer example 185 |
Data 186 | DataOptions 187 | Data Events 188 | Google Layer Data Example
189 | ngui-map data example 190 |
BicyclingLayer 191 | BicyclingLayerOptions 192 | BicyclingLayer Events 193 | Google Bycycling Layer Example
194 | ngui-map bicycling-layer example 195 |
TrafficLayer 196 | TrafficLayerOptions 197 | TrafficLayer Events 198 | Google Traffic Layer Example
199 | ngui-map traffic-layer example 200 |
TransitLayer 201 | TransitLayerOptions 202 | TransitLayer Events 203 | Google Transit Layer Example
204 | ngui-map transit-layer example 205 |
StreetViewPanorama 206 | StreetViewPanoramaOptions 207 | StreetViewPanorama Events 208 | Google Streetview Example
209 | ngui-map streetview-panorama example 210 |
AutoComplete 211 | AutoComplete Options 212 | AutoComplete Events 213 | Google Places Autocomplete Example
214 | ngui-map places-auto-complete example 215 |
DirectionsRenderer 216 | DirectionsRenderer Options 217 | DirectionsRenderer Events 218 | Google Directions Example
219 | ngui-map directions-renderer example 220 |
DrawingManager 221 | DrawingManager Options 222 | DrawingManager Events 223 | Google Drawing Manager Example
224 | ngui-map drawing-manager example 225 |
226 | 227 | ### Custom Directives 228 | 229 | * custom-marker 230 | * properties: position 231 | * event: all marker events. 232 | 233 | ### For Developers 234 | 235 | ### To start 236 | 237 | $ git clone https://github.com/ng2-ui/map.git 238 | $ cd map 239 | $ npm install 240 | $ npm start 241 | 242 | ### Before you make a PR 243 | If you are planning to make a lot of code changes in the PR, we encourage to create an issue first: 244 | 245 | 1. To avoid duplicate work 246 | 2. To encourage discussion of solutions 247 | 248 | We do not want to reject a PR because of you've chosen a wrong approach or direction after a lot of effort has been made. 249 | 250 | ### List of available npm tasks 251 | 252 | * `npm run` : List all available tasks 253 | * `npm start`: Run `app` directory for development using `webpack-dev-server` with port 9001 254 | * `npm run clean`: Remove dist folder 255 | * `npm run clean:dist`: Clean up unnecessary dist folder within dist and app directory 256 | * `npm run lint`: Lint TypeScript code 257 | * `npm run build:ngc`: build ES module 258 | * `npm run build:umd`: Build UMD module `map.umd.js` 259 | * `npm run build:app`: Build `app/build/app.js` for runnable examples 260 | * `npm run build`: Build all(build:ngc, build:umc, build:app, and clean:dist) 261 | --------------------------------------------------------------------------------