Skip to content

Commit

Permalink
feat(google-maps): implement new marker clusterer
Browse files Browse the repository at this point in the history
Adds a new `MapMarkerClusterer` component that is based on the most up-to-date clustering library, and supports both regular and advanced markers.

Fixes #23695.
  • Loading branch information
crisbeto committed Oct 17, 2024
1 parent a05475e commit c70aae1
Show file tree
Hide file tree
Showing 13 changed files with 799 additions and 40 deletions.
35 changes: 12 additions & 23 deletions src/dev-app/google-map/google-map-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,25 @@
(mapRightclick)="handleRightclick()"
[mapTypeId]="mapTypeId"
[mapId]="mapId">
<deprecated-map-marker-clusterer [imagePath]="markerClustererImagePath">
<map-marker #firstMarker="mapMarker"
[position]="center"
(mapClick)="infoWindow.open(firstMarker)"></map-marker>
<map-marker-clusterer>
<map-advanced-marker
#firstMarker="mapAdvancedMarker"
[position]="center"
(mapClick)="infoWindow.open(firstMarker)"></map-advanced-marker>
@for (markerPosition of markerPositions; track markerPosition) {
<map-marker #marker="mapMarker"
<map-advanced-marker #marker="mapAdvancedMarker"
[position]="markerPosition"
[options]="markerOptions"
(mapClick)="infoWindow.open(marker)"></map-marker>
(mapClick)="infoWindow.open(marker)"></map-advanced-marker>
}
</deprecated-map-marker-clusterer>
@if (hasAdvancedMarker) {
</map-marker-clusterer>
@if (hasCustomContentMarker) {
<map-advanced-marker
#secondMarker="mapAdvancedMarker"
(mapClick)="infoWindow.open(secondMarker)"
title="Advanced Marker"
[gmpDraggable]="false"
[content]="hasAdvancedMarkerCustomContent ? advancedMarkerContent : null"
[content]="advancedMarkerContent"
[position]="mapAdvancedMarkerPosition">

<svg #advancedMarkerContent fill="oklch(69.02% .277 332.77)" viewBox="0 0 960 960" width="50px" height="50px" xml:space="preserve">
<g>
<polygon points="562.6,109.8 804.1,629.5 829.2,233.1"/>
Expand Down Expand Up @@ -216,18 +215,8 @@

<div>
<label>
Toggle Advanced Marker
<input type="checkbox" [(ngModel)]="hasAdvancedMarker">
</label>
</div>

<div>
<label>
Toggle custom content for Advanced Marker
<input
type="checkbox"
[(ngModel)]="hasAdvancedMarkerCustomContent"
[disabled]="!hasAdvancedMarker">
Toggle Advanced Marker with custom content
<input type="checkbox" [(ngModel)]="hasCustomContentMarker">
</label>
</div>

Expand Down
15 changes: 4 additions & 11 deletions src/dev-app/google-map/google-map-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ import {
MapHeatmapLayer,
MapInfoWindow,
MapKmlLayer,
MapMarker,
DeprecatedMapMarkerClusterer,
MapPolygon,
MapPolyline,
MapRectangle,
MapTrafficLayer,
MapTransitLayer,
MapMarkerClusterer,
} from '@angular/google-maps';

const POLYLINE_PATH: google.maps.LatLngLiteral[] = [
Expand Down Expand Up @@ -75,8 +74,7 @@ let apiLoadingPromise: Promise<unknown> | null = null;
MapHeatmapLayer,
MapInfoWindow,
MapKmlLayer,
MapMarker,
DeprecatedMapMarkerClusterer,
MapMarkerClusterer,
MapAdvancedMarker,
MapPolygon,
MapPolyline,
Expand All @@ -98,7 +96,6 @@ export class GoogleMapDemo {

center = {lat: 24, lng: 12};
mapAdvancedMarkerPosition = {lat: 22, lng: 21};
markerOptions = {draggable: false};
markerPositions: google.maps.LatLngLiteral[] = [];
zoom = 4;
display?: google.maps.LatLngLiteral;
Expand Down Expand Up @@ -153,17 +150,13 @@ export class GoogleMapDemo {
isTrafficLayerDisplayed = false;
isTransitLayerDisplayed = false;
isBicyclingLayerDisplayed = false;
hasAdvancedMarker = false;
hasAdvancedMarkerCustomContent = true;
hasCustomContentMarker = false;
// This is necessary for testing advanced markers. It seems like any value works locally.
mapId = '123';

mapTypeId: google.maps.MapTypeId;
mapTypeIds = ['hybrid', 'roadmap', 'satellite', 'terrain'] as google.maps.MapTypeId[];

markerClustererImagePath =
'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m';

directionsResult?: google.maps.DirectionsResult;

constructor() {
Expand Down Expand Up @@ -262,7 +255,7 @@ export class GoogleMapDemo {

if (!apiLoadingPromise) {
apiLoadingPromise = this._loadScript(
'https://unpkg.com/@googlemaps/markerclustererplus/dist/index.min.js',
'https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js',
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/google-maps/google-maps-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {MapTrafficLayer} from './map-traffic-layer/map-traffic-layer';
import {MapTransitLayer} from './map-transit-layer/map-transit-layer';
import {MapHeatmapLayer} from './map-heatmap-layer/map-heatmap-layer';
import {MapAdvancedMarker} from './map-advanced-marker/map-advanced-marker';
import {MapMarkerClusterer} from './map-marker-clusterer/map-marker-clusterer';

const COMPONENTS = [
GoogleMap,
Expand All @@ -44,6 +45,7 @@ const COMPONENTS = [
MapRectangle,
MapTrafficLayer,
MapTransitLayer,
MapMarkerClusterer,
];

@NgModule({
Expand Down
21 changes: 19 additions & 2 deletions src/google-maps/map-advanced-marker/map-advanced-marker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import {

import {GoogleMap} from '../google-map/google-map';
import {MapEventManager} from '../map-event-manager';
import {Observable} from 'rxjs';
import {MapAnchorPoint} from '../map-anchor-point';
import {MAP_MARKER, MarkerDirective} from '../marker-utilities';
import {Observable} from 'rxjs';
import {take} from 'rxjs/operators';

/**
* Default options for the Google Maps marker component. Displays a marker
Expand All @@ -43,8 +45,16 @@ export const DEFAULT_MARKER_OPTIONS = {
@Directive({
selector: 'map-advanced-marker',
exportAs: 'mapAdvancedMarker',
providers: [
{
provide: MAP_MARKER,
useExisting: MapAdvancedMarker,
},
],
})
export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAnchorPoint {
export class MapAdvancedMarker
implements OnInit, OnChanges, OnDestroy, MapAnchorPoint, MarkerDirective
{
private readonly _googleMap = inject(GoogleMap);
private _ngZone = inject(NgZone);
private _eventManager = new MapEventManager(inject(NgZone));
Expand Down Expand Up @@ -262,6 +272,13 @@ export class MapAdvancedMarker implements OnInit, OnChanges, OnDestroy, MapAncho
return this.advancedMarker;
}

/** Returns a promise that resolves when the marker has been initialized. */
_resolveMarker(): Promise<google.maps.marker.AdvancedMarkerElement> {
return this.advancedMarker
? Promise.resolve(this.advancedMarker)
: this.markerInitialized.pipe(take(1)).toPromise();
}

/** Creates a combined options object using the passed-in options and the individual inputs. */
private _combineOptions(): google.maps.marker.AdvancedMarkerElementOptions {
const options = this._options || DEFAULT_MARKER_OPTIONS;
Expand Down
51 changes: 51 additions & 0 deletions src/google-maps/map-marker-clusterer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# MapMarkerClusterer

The `MapMarkerClusterer` component wraps the [`MarkerClusterer` class](https://googlemaps.github.io/js-markerclusterer/classes/MarkerClusterer.html) from the [Google Maps JavaScript MarkerClusterer Library](https://github.com/googlemaps/js-markerclusterer). The `MapMarkerClusterer` component displays a cluster of markers that are children of the `<map-marker-clusterer>` tag. Unlike the other Google Maps components, MapMarkerClusterer does not have an `options` input, so any input (listed in the [documentation](https://googlemaps.github.io/js-markerclusterer/) for the `MarkerClusterer` class) should be set directly.

## Loading the Library

Like the Google Maps JavaScript API, the MarkerClusterer library needs to be loaded separately. This can be accomplished by using this script tag:

```html
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
```

Additional information can be found by looking at [Marker Clustering](https://developers.google.com/maps/documentation/javascript/marker-clustering) in the Google Maps JavaScript API documentation.

## Example

```typescript
// google-map-demo.component.ts
import {Component} from '@angular/core';
import {GoogleMap, MapMarkerClusterer, MapAdvancedMarker} from '@angular/google-maps';

@Component({
selector: 'google-map-demo',
templateUrl: 'google-map-demo.html',
imports: [GoogleMap, MapMarkerClusterer, MapAdvancedMarker],
})
export class GoogleMapDemo {
center: google.maps.LatLngLiteral = {lat: 24, lng: 12};
zoom = 4;
markerPositions: google.maps.LatLngLiteral[] = [];

addMarker(event: google.maps.MapMouseEvent) {
this.markerPositions.push(event.latLng.toJSON());
}
}
```

```html
<google-map
height="400px"
width="750px"
[center]="center"
[zoom]="zoom"
(mapClick)="addMarker($event)">
<map-marker-clusterer>
@for (markerPosition of markerPositions; track $index) {
<map-advanced-marker [position]="markerPosition"/>
}
</map-marker-clusterer>
</google-map>
```
Loading

0 comments on commit c70aae1

Please sign in to comment.