-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(google-maps): add heatmap support #21489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# MapHeatmapLayer | ||
|
||
The `MapHeatmapLayer` directive wraps the [`google.maps.visualization.HeatmapLayer` class](https://developers.google.com/maps/documentation/javascript/reference/visualization#HeatmapLayer) from the Google Maps Visualization JavaScript API. It displays | ||
a heatmap layer on the map when it is a content child of a `GoogleMap` component. Like `GoogleMap`, | ||
this directive offers an `options` input as well as a convenience input for passing in the `data` | ||
that is shown on the heatmap. | ||
|
||
## Requirements | ||
|
||
In order to render a heatmap, the Google Maps JavaScript API has to be loaded with the | ||
`visualization` library. To load the library, you have to add `&libraries=visualization` to the | ||
script that loads the Google Maps API. E.g. | ||
|
||
**Before:** | ||
```html | ||
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script> | ||
``` | ||
|
||
**After:** | ||
```html | ||
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=visualization"></script> | ||
``` | ||
|
||
More information: https://developers.google.com/maps/documentation/javascript/heatmaplayer | ||
|
||
## Example | ||
|
||
```typescript | ||
// google-map-demo.component.ts | ||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
selector: 'google-map-demo', | ||
templateUrl: 'google-map-demo.html', | ||
}) | ||
export class GoogleMapDemo { | ||
center = {lat: 37.774546, lng: -122.433523}; | ||
zoom = 12; | ||
heatmapOptions = {radius: 5}; | ||
heatmapData = [ | ||
{lat: 37.782, lng: -122.447}, | ||
{lat: 37.782, lng: -122.445}, | ||
{lat: 37.782, lng: -122.443}, | ||
{lat: 37.782, lng: -122.441}, | ||
{lat: 37.782, lng: -122.439}, | ||
{lat: 37.782, lng: -122.437}, | ||
{lat: 37.782, lng: -122.435}, | ||
{lat: 37.785, lng: -122.447}, | ||
{lat: 37.785, lng: -122.445}, | ||
{lat: 37.785, lng: -122.443}, | ||
{lat: 37.785, lng: -122.441}, | ||
{lat: 37.785, lng: -122.439}, | ||
{lat: 37.785, lng: -122.437}, | ||
{lat: 37.785, lng: -122.435} | ||
]; | ||
} | ||
``` | ||
|
||
```html | ||
<!-- google-map-demo.component.html --> | ||
<google-map height="400px" width="750px" [center]="center" [zoom]="zoom"> | ||
<map-heatmap-layer [data]="heatmapData" [options]="heatmapOptions"></map-heatmap-layer> | ||
</google-map> | ||
``` |
166 changes: 166 additions & 0 deletions
166
src/google-maps/map-heatmap-layer/map-heatmap-layer.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import {Component, ViewChild} from '@angular/core'; | ||
import {waitForAsync, TestBed} from '@angular/core/testing'; | ||
|
||
import {DEFAULT_OPTIONS} from '../google-map/google-map'; | ||
|
||
import {GoogleMapsModule} from '../google-maps-module'; | ||
import { | ||
createMapConstructorSpy, | ||
createMapSpy, | ||
createHeatmapLayerConstructorSpy, | ||
createHeatmapLayerSpy, | ||
createLatLngSpy, | ||
createLatLngConstructorSpy | ||
} from '../testing/fake-google-map-utils'; | ||
import {HeatmapData, MapHeatmapLayer} from './map-heatmap-layer'; | ||
|
||
describe('MapHeatmapLayer', () => { | ||
let mapSpy: jasmine.SpyObj<google.maps.Map>; | ||
let latLngSpy: jasmine.SpyObj<google.maps.LatLng>; | ||
|
||
beforeEach(waitForAsync(() => { | ||
TestBed.configureTestingModule({ | ||
imports: [GoogleMapsModule], | ||
declarations: [TestApp], | ||
}); | ||
})); | ||
|
||
beforeEach(() => { | ||
TestBed.compileComponents(); | ||
mapSpy = createMapSpy(DEFAULT_OPTIONS); | ||
latLngSpy = createLatLngSpy(); | ||
createMapConstructorSpy(mapSpy).and.callThrough(); | ||
createLatLngConstructorSpy(latLngSpy).and.callThrough(); | ||
}); | ||
|
||
afterEach(() => { | ||
(window.google as any) = undefined; | ||
}); | ||
|
||
it('initializes a Google Map heatmap layer', () => { | ||
const heatmapSpy = createHeatmapLayerSpy(); | ||
const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.detectChanges(); | ||
|
||
expect(heatmapConstructorSpy).toHaveBeenCalledWith({ | ||
data: [], | ||
map: mapSpy, | ||
}); | ||
}); | ||
|
||
it('should throw if the `visualization` library has not been loaded', () => { | ||
createHeatmapLayerConstructorSpy(createHeatmapLayerSpy()); | ||
delete (window.google.maps as any).visualization; | ||
|
||
expect(() => { | ||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.detectChanges(); | ||
}).toThrowError(/Namespace `google.maps.visualization` not found, cannot construct heatmap/); | ||
}); | ||
|
||
it('sets heatmap inputs', () => { | ||
const options: google.maps.visualization.HeatmapLayerOptions = { | ||
map: mapSpy, | ||
data: [ | ||
new google.maps.LatLng(37.782, -122.447), | ||
new google.maps.LatLng(37.782, -122.445), | ||
new google.maps.LatLng(37.782, -122.443) | ||
] | ||
}; | ||
const heatmapSpy = createHeatmapLayerSpy(); | ||
const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.data = options.data; | ||
fixture.detectChanges(); | ||
|
||
expect(heatmapConstructorSpy).toHaveBeenCalledWith(options); | ||
}); | ||
|
||
it('sets heatmap options, ignoring map', () => { | ||
const options: Partial<google.maps.visualization.HeatmapLayerOptions> = { | ||
radius: 5, | ||
dissipating: true | ||
}; | ||
const data = [ | ||
new google.maps.LatLng(37.782, -122.447), | ||
new google.maps.LatLng(37.782, -122.445), | ||
new google.maps.LatLng(37.782, -122.443) | ||
]; | ||
const heatmapSpy = createHeatmapLayerSpy(); | ||
const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.data = data; | ||
fixture.componentInstance.options = options; | ||
fixture.detectChanges(); | ||
|
||
expect(heatmapConstructorSpy).toHaveBeenCalledWith({...options, map: mapSpy, data}); | ||
}); | ||
|
||
it('exposes methods that provide information about the heatmap', () => { | ||
const heatmapSpy = createHeatmapLayerSpy(); | ||
createHeatmapLayerConstructorSpy(heatmapSpy).and.callThrough(); | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.detectChanges(); | ||
const heatmap = fixture.componentInstance.heatmap; | ||
|
||
heatmapSpy.getData.and.returnValue([] as any); | ||
expect(heatmap.getData()).toEqual([]); | ||
}); | ||
|
||
it('should update the heatmap data when the input changes', () => { | ||
const heatmapSpy = createHeatmapLayerSpy(); | ||
const heatmapConstructorSpy = createHeatmapLayerConstructorSpy(heatmapSpy).and.callThrough(); | ||
let data = [ | ||
new google.maps.LatLng(1, 2), | ||
new google.maps.LatLng(3, 4), | ||
new google.maps.LatLng(5, 6) | ||
]; | ||
|
||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.data = data; | ||
fixture.detectChanges(); | ||
|
||
expect(heatmapConstructorSpy).toHaveBeenCalledWith(jasmine.objectContaining({data})); | ||
data = [ | ||
new google.maps.LatLng(7, 8), | ||
new google.maps.LatLng(9, 10), | ||
new google.maps.LatLng(11, 12) | ||
]; | ||
fixture.componentInstance.data = data; | ||
fixture.detectChanges(); | ||
|
||
expect(heatmapSpy.setData).toHaveBeenCalledWith(data); | ||
}); | ||
|
||
it('should create a LatLng object if a LatLngLiteral is passed in', () => { | ||
const latLngConstructor = createLatLngConstructorSpy(latLngSpy).and.callThrough(); | ||
createHeatmapLayerConstructorSpy(createHeatmapLayerSpy()).and.callThrough(); | ||
const fixture = TestBed.createComponent(TestApp); | ||
fixture.componentInstance.data = [{lat: 1, lng: 2}, {lat: 3, lng: 4}]; | ||
fixture.detectChanges(); | ||
|
||
expect(latLngConstructor).toHaveBeenCalledWith(1, 2); | ||
expect(latLngConstructor).toHaveBeenCalledWith(3, 4); | ||
expect(latLngConstructor).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
}); | ||
|
||
@Component({ | ||
selector: 'test-app', | ||
template: ` | ||
<google-map> | ||
<map-heatmap-layer [data]="data" [options]="options"> | ||
</map-heatmap-layer> | ||
</google-map>`, | ||
}) | ||
class TestApp { | ||
@ViewChild(MapHeatmapLayer) heatmap: MapHeatmapLayer; | ||
options?: Partial<google.maps.visualization.HeatmapLayerOptions>; | ||
data?: HeatmapData; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several classes that we may want to implement in the future that also require adding separate libraries. Should we make this note more general, to apply to several components, while giving more specific instructions about which library to use in the component-specific readme?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it may be too vague if we're doing it now since there aren't any other components that use this approach. It might be better to do it once we have other components.