Skip to content

Commit

Permalink
feat(cluster): add clusterComponent!
Browse files Browse the repository at this point in the history
  • Loading branch information
Wykks committed Dec 19, 2017
1 parent 390bf45 commit 1cb6daa
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 14 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Include the following components:
- mglNavigation
- [mgl-marker](https://github.com/Wykks/ngx-mapbox-gl/wiki/API-Documentation#mgl-marker-mapbox-gl-api)
- [mgl-popup](https://github.com/Wykks/ngx-mapbox-gl/wiki/API-Documentation#mgl-popup-mapbox-gl-api)
- mgl-cluster (cluster of html marker!)

## How to start

Expand Down
3 changes: 2 additions & 1 deletion e2e/runtime-check.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ describe('Generic runtime error check', () => {
'center-on-symbol',
'ngx-drag-a-point',
'hover-styles',
'popup-on-click'
'popup-on-click',
'ngx-marker-cluster'
].forEach((route: string) => {
it(`should display a map without errors for /${route}`, async () => {
await browser.get(`/${route}`);
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@types/lodash-es": "^4.17.0",
"@types/mapbox-gl": "^0.42.0",
"@types/node": "~8.5.1",
"@types/supercluster": "^3.0.2",
"codelyzer": "~4.0.2",
"core-js": "^2.5.3",
"jasmine-core": "~2.8.0",
Expand All @@ -95,5 +96,9 @@
"tslint-consistent-codestyle": "^1.11.0",
"typescript": "~2.5.3",
"zone.js": "^0.8.18"
},
"dependencies": {
"@turf/bbox": "^5.1.5",
"@turf/helpers": "^5.1.5"
}
}
22 changes: 22 additions & 0 deletions src/app/demo/examples/ngx-marker-cluster.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.marker-cluster {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #4f615a;
display: flex;
justify-content: center;
align-items: center;
color: white;
border: 2px solid #56C498;
}

.marker {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #7d7d7d;
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #C9C9C9
}
47 changes: 47 additions & 0 deletions src/app/demo/examples/ngx-marker-cluster.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Component } from '@angular/core';

const earthquakes = require('./earthquakes.geo.json');

@Component({
template: `
<mgl-map
style="mapbox://styles/mapbox/dark-v9"
[zoom]="[3]"
[center]="[-103.59179687498357, 40.66995747013945]"
>
<mgl-cluster
[data]="earthquakes"
[maxZoom]="14"
[radius]="50"
>
<ng-template mglPoint let-feature>
<mgl-marker
[feature]="feature"
>
<div
class="marker"
[title]="feature.properties['Secondary ID']"
>
{{ feature.properties['Primary ID'] }}
</div>
</mgl-marker>
</ng-template>
<ng-template mglClusterPoint let-feature>
<mgl-marker
[feature]="feature"
>
<div
class="marker-cluster"
>
{{ feature.properties['point_count'] }}
</div>
</mgl-marker>
</ng-template>
</mgl-cluster>
</mgl-map>
`,
styleUrls: ['./examples.css', './ngx-marker-cluster.component.css']
})
export class NgxMarkerClusterComponent {
earthquakes = earthquakes;
}
5 changes: 4 additions & 1 deletion src/app/demo/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { NgxCustomControlComponent } from './examples/ngx-custom-control.compone
import { NgxCustomMarkerIconsComponent } from './examples/ngx-custom-marker-icons.component';
import { NgxDragAPointComponent } from './examples/ngx-drag-a-point.component';
import { NgxGeoJSONLineComponent } from './examples/ngx-geojson-line.component';
import { NgxMarkerClusterComponent } from './examples/ngx-marker-cluster.component';
import { NgxScaleControlComponent } from './examples/ngx-scale-control.component';
import { PopupOnClickComponent } from './examples/popup-on-click.component';
import { PopupComponent } from './examples/popup.component';
Expand Down Expand Up @@ -81,6 +82,7 @@ export const demoRoutes: Routes = [
{ path: 'hover-styles', component: HoverStylesComponent, data: { label: 'Create a hover effect', cat: Category.USER_INTERACTION } },
{ path: 'popup-on-click', component: PopupOnClickComponent, data: { label: 'Display a popup on click', cat: Category.CONTROLS_AND_OVERLAYS } },
{ path: 'zoomto-linestring', component: ZoomtoLinestringComponent, data: { label: 'Fit to the bounds of a LineString', cat: Category.USER_INTERACTION } },
{ path: 'ngx-marker-cluster', component: NgxMarkerClusterComponent, data: { label: '[NGX] Create a clusters of html markers', cat: Category.CONTROLS_AND_OVERLAYS } }
]
}
];
Expand Down Expand Up @@ -131,7 +133,8 @@ export const demoRoutes: Routes = [
NgxDragAPointComponent,
HoverStylesComponent,
PopupOnClickComponent,
ZoomtoLinestringComponent
ZoomtoLinestringComponent,
NgxMarkerClusterComponent
]
})
export class DemoModule { }
105 changes: 105 additions & 0 deletions src/app/lib/cluster/cluster.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { merge } from 'rxjs/observable/merge';
import { fromEvent } from 'rxjs/observable/fromEvent';
import { startWith } from 'rxjs/operators/startWith';
import { Subscription } from 'rxjs/Subscription';
import supercluster, { Supercluster } from 'supercluster';
import { MapService } from '../map/map.service';
import {
Component,
Input,
OnChanges,
OnDestroy,
SimpleChanges,
OnInit,
ChangeDetectionStrategy,
Directive,
TemplateRef,
ContentChild,
AfterContentInit,
ChangeDetectorRef,
} from '@angular/core';

@Directive({ selector: 'ng-template[mglPoint]' })
export class PointDirective { }

@Directive({ selector: 'ng-template[mglClusterPoint]' })
export class ClusterPointDirective { }

@Component({
selector: 'mgl-cluster',
template: `
<ng-container *ngFor="let feature of clusterPoints">
<ng-container *ngIf="feature.properties.cluster; else point">
<ng-container *ngTemplateOutlet="clusterPointTpl; context: { $implicit: feature }"></ng-container>
</ng-container>
<ng-template #point>
<ng-container *ngTemplateOutlet="pointTpl; context: { $implicit: feature }"></ng-container>
</ng-template>
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
preserveWhitespaces: false
})
export class ClusterComponent implements OnChanges, OnDestroy, AfterContentInit, OnInit {
/* Init input */
@Input() radius: any;
@Input() maxZoom: any;
@Input() minZoom: any;
@Input() extent: any;
@Input() nodeSize: any;
@Input() log: any;

/* Dynamic input */
@Input() data: GeoJSON.FeatureCollection<GeoJSON.Point>;

@ContentChild(PointDirective, { read: TemplateRef }) pointTpl: TemplateRef<any>;
@ContentChild(ClusterPointDirective, { read: TemplateRef }) clusterPointTpl: TemplateRef<any>;

clusterPoints: GeoJSON.Feature<GeoJSON.Point>[];

private supercluster: Supercluster;
private sub = new Subscription();

constructor(
private MapService: MapService,
private ChangeDetectorRef: ChangeDetectorRef
) { }

ngOnInit() {
this.supercluster = supercluster({
radius: this.radius,
maxZoom: this.maxZoom
});
this.supercluster.load(this.data.features);
}

ngOnChanges(changes: SimpleChanges) {
if (changes.data && !changes.data.isFirstChange()) {
this.supercluster.load(this.data.features);
}
}

ngAfterContentInit() {
this.MapService.mapCreated$.subscribe(() => {
const mapMove$ = merge(
fromEvent(this.MapService.mapInstance, 'zoomChange'),
fromEvent(this.MapService.mapInstance, 'move')
);
const sub = mapMove$.pipe(
startWith<any>(undefined)
).subscribe(() => this.updateCluster());
this.sub.add(sub);
});
}

ngOnDestroy() {
this.sub.unsubscribe();
}

private updateCluster() {
const bbox = this.MapService.getCurrentViewportBbox();
const currentZoom = Math.round(this.MapService.mapInstance.getZoom());
this.clusterPoints = this.supercluster.getClusters(bbox, currentZoom);
this.ChangeDetectorRef.detectChanges();
}
}
31 changes: 19 additions & 12 deletions src/app/lib/lib.module.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { AttributionControlDirective } from './control/attribution-control.directive';
import { ControlComponent } from './control/control.component';
import { FullscreenControlDirective } from './control/fullscreen-control.directive';
import { GeolocateControlDirective } from './control/geolocate-control.directive';
import { NavigationControlDirective } from './control/navigation-control.directive';
import { ScaleControlDirective } from './control/scale-control.directive';
import { ImageComponent } from './image/image.component';
import { LayerComponent } from './layer/layer.component';
import { MapComponent } from './map/map.component';
import { MAPBOX_API_KEY } from './map/map.service';
import { ClusterComponent, ClusterPointDirective, PointDirective } from './cluster/cluster.component';
import { MarkerComponent } from './marker/marker.component';
import { PopupComponent } from './popup/popup.component';
import { CanvasSourceComponent } from './source/canvas-source.component';
import { DraggableDirective } from './source/geojson/draggable.directive';
import { FeatureComponent } from './source/geojson/feature.component';
import { GeoJSONSourceComponent } from './source/geojson/geojson-source.component';
import { ImageSourceComponent } from './source/image-source.component';
import { RasterSourceComponent } from './source/raster-source.component';
import { VectorSourceComponent } from './source/vector-source.component';
import { VideoSourceComponent } from './source/video-source.component';
import { FeatureComponent } from './source/geojson/feature.component';
import { DraggableDirective } from './source/geojson/draggable.directive';
import { MarkerComponent } from './marker/marker.component';
import { PopupComponent } from './popup/popup.component';
import { ControlComponent } from './control/control.component';
import { FullscreenControlDirective } from './control/fullscreen-control.directive';
import { NavigationControlDirective } from './control/navigation-control.directive';
import { GeolocateControlDirective } from './control/geolocate-control.directive';
import { AttributionControlDirective } from './control/attribution-control.directive';
import { ScaleControlDirective } from './control/scale-control.directive';

@NgModule({
imports: [
Expand All @@ -44,7 +45,10 @@ import { ScaleControlDirective } from './control/scale-control.directive';
NavigationControlDirective,
GeolocateControlDirective,
AttributionControlDirective,
ScaleControlDirective
ScaleControlDirective,
PointDirective,
ClusterPointDirective,
ClusterComponent
],
exports: [
MapComponent,
Expand All @@ -65,7 +69,10 @@ import { ScaleControlDirective } from './control/scale-control.directive';
NavigationControlDirective,
GeolocateControlDirective,
AttributionControlDirective,
ScaleControlDirective
ScaleControlDirective,
PointDirective,
ClusterPointDirective,
ClusterComponent
]
})
export class NgxMapboxGLModule {
Expand Down
14 changes: 14 additions & 0 deletions src/app/lib/map/map.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { EventEmitter, Inject, Injectable, InjectionToken, NgZone, Optional } from '@angular/core';
import bbox from '@turf/bbox';
import { polygon } from '@turf/helpers';
import * as MapboxGl from 'mapbox-gl';
import { AsyncSubject } from 'rxjs/AsyncSubject';
import { Observable } from 'rxjs/Observable';
import { first } from 'rxjs/operators/first';
import { BBox } from 'supercluster';
import { MapEvent, MapImageData, MapImageOptions } from './map.types';

export const MAPBOX_API_KEY = new InjectionToken('MapboxApiKey');
Expand Down Expand Up @@ -357,6 +360,17 @@ export class MapService {
});
}

getCurrentViewportBbox(): BBox {
const canvas = this.mapInstance.getCanvas();
const w = canvas.width;
const h = canvas.height;
const upLeft = this.mapInstance.unproject([0, 0]).toArray();
const upRight = this.mapInstance.unproject([w, 0]).toArray();
const downRight = this.mapInstance.unproject([w, h]).toArray();
const downLeft = this.mapInstance.unproject([0, h]).toArray();
return bbox(polygon([[upLeft, upRight, downRight, downLeft, upLeft]]));
}

private createMap(options: MapboxGl.MapboxOptions) {
Object.keys(options)
.forEach((key: string) => {
Expand Down
19 changes: 19 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,23 @@
version "0.0.11"
resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.0.11.tgz#c8f70f270ed38f29b2873248126fd59abd635862"

"@turf/bbox@^5.1.5":
version "5.1.5"
resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-5.1.5.tgz#3051df514ad4c50f4a4f9b8a2d15fd8b6840eda3"
dependencies:
"@turf/helpers" "^5.1.5"
"@turf/meta" "^5.1.5"

"@turf/helpers@^5.1.5":
version "5.1.5"
resolved "https://registry.yarnpkg.com/@turf/helpers/-/helpers-5.1.5.tgz#153405227ab933d004a5bb9641a9ed999fcbe0cf"

"@turf/meta@^5.1.5":
version "5.1.6"
resolved "https://registry.yarnpkg.com/@turf/meta/-/meta-5.1.6.tgz#c20a863eded0869fb28548dee889341bccb46a46"
dependencies:
"@turf/helpers" "^5.1.5"

"@turf/random@^5.1.5":
version "5.1.5"
resolved "https://registry.yarnpkg.com/@turf/random/-/random-5.1.5.tgz#b32efc934560ae8ba57e8ebb51f241c39fba2e7b"
Expand Down Expand Up @@ -294,6 +307,12 @@
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1"

"@types/supercluster@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/supercluster/-/supercluster-3.0.2.tgz#dc11a6afa3d21b1a3d8510b1759a668fdf19e254"
dependencies:
"@types/geojson" "*"

JSONStream@^1.0.4:
version "1.3.1"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
Expand Down

0 comments on commit 1cb6daa

Please sign in to comment.