Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit 2e4878b

Browse files
authored
feat(core): add rectangle support (#1450)
add rectangle functionality closes #570
1 parent 9c76e7e commit 2e4878b

9 files changed

Lines changed: 768 additions & 3 deletions

File tree

packages/core/core.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {ModuleWithProviders, NgModule} from '@angular/core';
22
import {AgmMap} from './directives/map';
33
import {AgmCircle} from './directives/circle';
4+
import {AgmRectangle} from './directives/rectangle';
45
import {AgmInfoWindow} from './directives/info-window';
56
import {AgmMarker} from './directives/marker';
67
import {AgmPolygon} from './directives/polygon';
@@ -18,7 +19,7 @@ import {BROWSER_GLOBALS_PROVIDERS} from './utils/browser-globals';
1819
*/
1920
export function coreDirectives() {
2021
return [
21-
AgmMap, AgmMarker, AgmInfoWindow, AgmCircle,
22+
AgmMap, AgmMarker, AgmInfoWindow, AgmCircle, AgmRectangle,
2223
AgmPolygon, AgmPolyline, AgmPolylinePoint, AgmKmlLayer,
2324
AgmDataLayer
2425
];

packages/core/directives.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export {AgmMap} from './directives/map';
22
export {AgmCircle} from './directives/circle';
3+
export {AgmRectangle} from './directives/rectangle';
34
export {AgmInfoWindow} from './directives/info-window';
45
export {AgmKmlLayer} from './directives/kml-layer';
56
export {AgmDataLayer} from './directives/data-layer';

packages/core/directives/map.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
RotateControlOptions, ScaleControlOptions, StreetViewControlOptions, ZoomControlOptions} from '../services/google-maps-types';
99
import {LatLngBounds, LatLngBoundsLiteral, MapTypeStyle} from '../services/google-maps-types';
1010
import {CircleManager} from '../services/managers/circle-manager';
11+
import {RectangleManager} from '../services/managers/rectangle-manager';
1112
import {InfoWindowManager} from '../services/managers/info-window-manager';
1213
import {MarkerManager} from '../services/managers/marker-manager';
1314
import {PolygonManager} from '../services/managers/polygon-manager';
@@ -41,8 +42,8 @@ import {DataLayerManager} from './../services/managers/data-layer-manager';
4142
@Component({
4243
selector: 'agm-map',
4344
providers: [
44-
GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, PolylineManager,
45-
PolygonManager, KmlLayerManager, DataLayerManager
45+
GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, RectangleManager,
46+
PolylineManager, PolygonManager, KmlLayerManager, DataLayerManager
4647
],
4748
host: {
4849
// todo: deprecated - we will remove it with the next version
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
import {
2+
Directive,
3+
EventEmitter,
4+
OnChanges,
5+
OnDestroy,
6+
OnInit,
7+
SimpleChange,
8+
Input,
9+
Output
10+
} from '@angular/core';
11+
import { Subscription } from 'rxjs';
12+
import { MouseEvent } from '../map-types';
13+
import {
14+
LatLngBounds,
15+
LatLngBoundsLiteral
16+
} from '../services/google-maps-types';
17+
import { MouseEvent as MapMouseEvent } from '../services/google-maps-types';
18+
import { RectangleManager } from '../services/managers/rectangle-manager';
19+
20+
@Directive({
21+
selector: 'agm-rectangle'
22+
})
23+
export class AgmRectangle implements OnInit, OnChanges, OnDestroy {
24+
/**
25+
* The north position of the rectangle (required).
26+
*/
27+
@Input() north: number;
28+
29+
/**
30+
* The east position of the rectangle (required).
31+
*/
32+
@Input() east: number;
33+
34+
/**
35+
* The south position of the rectangle (required).
36+
*/
37+
@Input() south: number;
38+
39+
/**
40+
* The west position of the rectangle (required).
41+
*/
42+
@Input() west: number;
43+
44+
/**
45+
* Indicates whether this Rectangle handles mouse events. Defaults to true.
46+
*/
47+
@Input() clickable: boolean = true;
48+
49+
/**
50+
* If set to true, the user can drag this rectangle over the map. Defaults to false.
51+
*/
52+
// tslint:disable-next-line:no-input-rename
53+
@Input('rectangleDraggable') draggable: boolean = false;
54+
55+
/**
56+
* If set to true, the user can edit this rectangle by dragging the control points shown at
57+
* the center and around the circumference of the rectangle. Defaults to false.
58+
*/
59+
@Input() editable: boolean = false;
60+
61+
/**
62+
* The fill color. All CSS3 colors are supported except for extended named colors.
63+
*/
64+
@Input() fillColor: string;
65+
66+
/**
67+
* The fill opacity between 0.0 and 1.0.
68+
*/
69+
@Input() fillOpacity: number;
70+
71+
/**
72+
* The stroke color. All CSS3 colors are supported except for extended named colors.
73+
*/
74+
@Input() strokeColor: string;
75+
76+
/**
77+
* The stroke opacity between 0.0 and 1.0
78+
*/
79+
@Input() strokeOpacity: number;
80+
81+
/**
82+
* The stroke position. Defaults to CENTER.
83+
* This property is not supported on Internet Explorer 8 and earlier.
84+
*/
85+
@Input() strokePosition: 'CENTER' | 'INSIDE' | 'OUTSIDE' = 'CENTER';
86+
87+
/**
88+
* The stroke width in pixels.
89+
*/
90+
@Input() strokeWeight: number = 0;
91+
92+
/**
93+
* Whether this rectangle is visible on the map. Defaults to true.
94+
*/
95+
@Input() visible: boolean = true;
96+
97+
/**
98+
* The zIndex compared to other polys.
99+
*/
100+
@Input() zIndex: number;
101+
102+
/**
103+
* This event is fired when the rectangle's is changed.
104+
*/
105+
@Output()
106+
boundsChange: EventEmitter<LatLngBoundsLiteral> = new EventEmitter<
107+
LatLngBoundsLiteral
108+
>();
109+
110+
/**
111+
* This event emitter gets emitted when the user clicks on the rectangle.
112+
*/
113+
@Output()
114+
rectangleClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
115+
116+
/**
117+
* This event emitter gets emitted when the user clicks on the rectangle.
118+
*/
119+
@Output()
120+
rectangleDblClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
121+
122+
/**
123+
* This event is repeatedly fired while the user drags the rectangle.
124+
*/
125+
@Output() drag: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
126+
127+
/**
128+
* This event is fired when the user stops dragging the rectangle.
129+
*/
130+
@Output() dragEnd: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
131+
132+
/**
133+
* This event is fired when the user starts dragging the rectangle.
134+
*/
135+
@Output()
136+
dragStart: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
137+
138+
/**
139+
* This event is fired when the DOM mousedown event is fired on the rectangle.
140+
*/
141+
@Output()
142+
mouseDown: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
143+
144+
/**
145+
* This event is fired when the DOM mousemove event is fired on the rectangle.
146+
*/
147+
@Output()
148+
mouseMove: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
149+
150+
/**
151+
* This event is fired on rectangle mouseout.
152+
*/
153+
@Output() mouseOut: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
154+
155+
/**
156+
* This event is fired on rectangle mouseover.
157+
*/
158+
@Output()
159+
mouseOver: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
160+
161+
/**
162+
* This event is fired when the DOM mouseup event is fired on the rectangle.
163+
*/
164+
@Output() mouseUp: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
165+
166+
/**
167+
* This event is fired when the rectangle is right-clicked on.
168+
*/
169+
@Output()
170+
rightClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
171+
172+
private _rectangleAddedToManager: boolean = false;
173+
174+
private static _mapOptions: string[] = [
175+
'fillColor',
176+
'fillOpacity',
177+
'strokeColor',
178+
'strokeOpacity',
179+
'strokePosition',
180+
'strokeWeight',
181+
'visible',
182+
'zIndex',
183+
'clickable'
184+
];
185+
186+
private _eventSubscriptions: Subscription[] = [];
187+
188+
constructor(private _manager: RectangleManager) {}
189+
190+
/** @internal */
191+
ngOnInit() {
192+
this._manager.addRectangle(this);
193+
this._rectangleAddedToManager = true;
194+
this._registerEventListeners();
195+
}
196+
197+
/** @internal */
198+
ngOnChanges(changes: { [key: string]: SimpleChange }) {
199+
if (!this._rectangleAddedToManager) {
200+
return;
201+
}
202+
if (
203+
changes['north'] ||
204+
changes['east'] ||
205+
changes['south'] ||
206+
changes['west']
207+
) {
208+
this._manager.setBounds(this);
209+
}
210+
if (changes['editable']) {
211+
this._manager.setEditable(this);
212+
}
213+
if (changes['draggable']) {
214+
this._manager.setDraggable(this);
215+
}
216+
if (changes['visible']) {
217+
this._manager.setVisible(this);
218+
}
219+
this._updateRectangleOptionsChanges(changes);
220+
}
221+
222+
private _updateRectangleOptionsChanges(changes: {
223+
[propName: string]: SimpleChange;
224+
}) {
225+
let options: { [propName: string]: any } = {};
226+
let optionKeys = Object.keys(changes).filter(
227+
k => AgmRectangle._mapOptions.indexOf(k) !== -1
228+
);
229+
optionKeys.forEach(k => {
230+
options[k] = changes[k].currentValue;
231+
});
232+
if (optionKeys.length > 0) {
233+
this._manager.setOptions(this, options);
234+
}
235+
}
236+
237+
private _registerEventListeners() {
238+
let events: Map<string, EventEmitter<any>> = new Map<
239+
string,
240+
EventEmitter<any>
241+
>();
242+
events.set('bounds_changed', this.boundsChange);
243+
events.set('click', this.rectangleClick);
244+
events.set('dblclick', this.rectangleDblClick);
245+
events.set('drag', this.drag);
246+
events.set('dragend', this.dragEnd);
247+
events.set('dragStart', this.dragStart);
248+
events.set('mousedown', this.mouseDown);
249+
events.set('mousemove', this.mouseMove);
250+
events.set('mouseout', this.mouseOut);
251+
events.set('mouseover', this.mouseOver);
252+
events.set('mouseup', this.mouseUp);
253+
events.set('rightclick', this.rightClick);
254+
255+
events.forEach((eventEmitter, eventName) => {
256+
this._eventSubscriptions.push(
257+
this._manager
258+
.createEventObservable<MapMouseEvent>(eventName, this)
259+
.subscribe(value => {
260+
switch (eventName) {
261+
case 'bounds_changed':
262+
this._manager.getBounds(this).then(bounds =>
263+
eventEmitter.emit(<LatLngBoundsLiteral>{
264+
north: bounds.getNorthEast().lat(),
265+
east: bounds.getNorthEast().lng(),
266+
south: bounds.getSouthWest().lat(),
267+
west: bounds.getSouthWest().lng()
268+
})
269+
);
270+
break;
271+
default:
272+
eventEmitter.emit(<MouseEvent>{
273+
coords: { lat: value.latLng.lat(), lng: value.latLng.lng() }
274+
});
275+
}
276+
})
277+
);
278+
});
279+
}
280+
281+
/** @internal */
282+
ngOnDestroy() {
283+
this._eventSubscriptions.forEach(function(s: Subscription) {
284+
s.unsubscribe();
285+
});
286+
this._eventSubscriptions = null;
287+
this._manager.removeRectangle(this);
288+
}
289+
290+
/**
291+
* Gets the LatLngBounds of this Rectangle.
292+
*/
293+
getBounds(): Promise<LatLngBounds> {
294+
return this._manager.getBounds(this);
295+
}
296+
}

packages/core/services.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export {GoogleMapsAPIWrapper} from './services/google-maps-api-wrapper';
22
export {CircleManager} from './services/managers/circle-manager';
3+
export {RectangleManager} from './services/managers/rectangle-manager';
34
export {InfoWindowManager} from './services/managers/info-window-manager';
45
export {MarkerManager} from './services/managers/marker-manager';
56
export {PolygonManager} from './services/managers/polygon-manager';

packages/core/services/google-maps-api-wrapper.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ export class GoogleMapsAPIWrapper {
6464
});
6565
}
6666

67+
/**
68+
* Creates a google.map.Rectangle for the current map.
69+
*/
70+
createRectangle(options: mapTypes.RectangleOptions): Promise<mapTypes.Rectangle> {
71+
return this._map.then((map: mapTypes.GoogleMap) => {
72+
options.map = map;
73+
return new google.maps.Rectangle(options);
74+
});
75+
}
76+
6777
createPolyline(options: PolylineOptions): Promise<Polyline> {
6878
return this.getNativeMap().then((map: mapTypes.GoogleMap) => {
6979
let line = new google.maps.Polyline(options);

0 commit comments

Comments
 (0)