Skip to content

Commit

Permalink
feat: feature draggable directive + example
Browse files Browse the repository at this point in the history
  • Loading branch information
Wykks committed Oct 29, 2017
1 parent 40cdb28 commit 1166346
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 54 deletions.
44 changes: 44 additions & 0 deletions src/app/demo/examples/ngx-drag-a-point.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component } from '@angular/core';

@Component({
template: `
<mgl-map
[style]="'mapbox://styles/mapbox/streets-v9'"
[zoom]="2"
[center]="[0, 0]"
>
<mgl-geojson-source
id="point"
>
<mgl-feature
[properties]=""
[geometry]="{
'type': 'Point',
'coordinates': [0, 0]
}"
[mglDraggable]="targetLayer"
></mgl-feature>
</mgl-geojson-source>
<mgl-layer
#targetLayer
id="point"
type="circle"
source="point"
[paint]="layerPaint"
(mouseEnter)="changeColor('#3bb2d0')"
(mouseLeave)="changeColor('#3887be')"
></mgl-layer>
</mgl-map>
`,
styleUrls: ['./examples.css']
})
export class NgxDragAPointComponent {
layerPaint = {
'circle-radius': 10,
'circle-color': '#3887be'
};

changeColor(color: string) {
this.layerPaint = { ...this.layerPaint, 'circle-color': color };
}
}
5 changes: 4 additions & 1 deletion src/app/demo/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { NgxCustomControlComponent } from './examples/ngx-custom-control.compone
import { InteractiveFalseComponent } from './examples/interactive-false.component';
import { LanguageSwitchComponent } from './examples/language-switch.component';
import { CenterOnSymbolComponent } from './examples/center-on-symbol.component';
import { NgxDragAPointComponent } from './examples/ngx-drag-a-point.component';

export const demoRoutes: Routes = [
{ path: '', component: IndexComponent },
Expand Down Expand Up @@ -60,6 +61,7 @@ export const demoRoutes: Routes = [
{ path: 'interactive-false', component: InteractiveFalseComponent },
{ path: 'language-switch', component: LanguageSwitchComponent },
{ path: 'center-on-symbol', component: CenterOnSymbolComponent },
{ path: 'ngx-drag-a-point', component: NgxDragAPointComponent },
];

@NgModule({
Expand Down Expand Up @@ -102,7 +104,8 @@ export const demoRoutes: Routes = [
NgxCustomControlComponent,
InteractiveFalseComponent,
LanguageSwitchComponent,
CenterOnSymbolComponent
CenterOnSymbolComponent,
NgxDragAPointComponent
]
})
export class DemoModule { }
32 changes: 21 additions & 11 deletions src/app/lib/layer/layer.component.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
import {
Component,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import {
BackgroundLayout,
BackgroundPaint,
CircleLayout,
CirclePaint,
FillExtrusionLayout,
FillExtrusionPaint,
FillLayout,
FillPaint,
GeoJSONSource,
GeoJSONSourceRaw,
ImageSource,
Layer,
LineLayout,
LinePaint,
MapMouseEvent,
RasterLayout,
RasterPaint,
RasterSource,
SymbolLayout,
VectorSource,
VideoSource,
BackgroundPaint,
FillPaint,
FillExtrusionPaint,
LinePaint,
SymbolPaint,
RasterPaint,
CirclePaint,
MapMouseEvent
VectorSource,
VideoSource
} from 'mapbox-gl';
import 'rxjs/add/operator/switchMap';
import { MapService } from '../map/map.service';

@Component({
Expand Down Expand Up @@ -56,7 +66,7 @@ export class LayerComponent implements OnInit, OnDestroy, OnChanges, Layer {
) { }

ngOnInit() {
this.MapService.mapLoaded$.subscribe(() => {
this.MapService.mapCreated$.switchMap(() => this.MapService.mapEvents.load).first().subscribe(() => {
this.MapService.addLayer({
layerOptions: {
id: this.id,
Expand Down
3 changes: 3 additions & 0 deletions src/app/lib/lib.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ 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';
Expand All @@ -27,6 +28,7 @@ import { ScaleControlDirective } from './control/scale-control.directive';
declarations: [
MapComponent,
LayerComponent,
DraggableDirective,
ImageComponent,
VectorSourceComponent,
GeoJSONSourceComponent,
Expand All @@ -47,6 +49,7 @@ import { ScaleControlDirective } from './control/scale-control.directive';
exports: [
MapComponent,
LayerComponent,
DraggableDirective,
ImageComponent,
VectorSourceComponent,
GeoJSONSourceComponent,
Expand Down
4 changes: 3 additions & 1 deletion src/app/lib/map/map.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ export class MapComponent implements OnChanges, OnDestroy, AfterViewInit, Mapbox
});
}

ngOnDestroy() { }
ngOnDestroy() {
this.mapInstance.remove();
}

ngOnChanges(changes: SimpleChanges) {
if (changes.minZoom && !changes.minZoom.isFirstChange()) {
Expand Down
6 changes: 3 additions & 3 deletions src/app/lib/map/map.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,21 @@ describe('MapService', () => {
}));

it('should fire load event', (done: DoneFn) => {
mapEvents.load.first().subscribe(() => {
mapEvents.mapEvents.load.first().subscribe(() => {
done();
});
});

it('should update minZoom', (done: DoneFn) => inject([MapService], (service: MapService) => {
mapEvents.load.first().subscribe(() => {
mapEvents.mapEvents.load.first().subscribe(() => {
service.updateMinZoom(6);
expect(service.mapInstance.getMinZoom()).toEqual(6);
done();
});
})());

// xit('should update zoom', (done: DoneFn) => inject([MapService], (service: MapService) => {
// mapEvents.load.first().subscribe(() => {
// mapEvents.mapEvents.load.first().subscribe(() => {
// service.prepareZoom(6);
// service.startMoveIfNeeded
// done();
Expand Down
45 changes: 26 additions & 19 deletions src/app/lib/map/map.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,15 @@ export type AllSource = MapboxGl.VectorSource |
export class MapService {
mapInstance: MapboxGl.Map;
mapCreated$: Observable<void>;
mapLoaded$: Observable<void>;
mapEvents: MapEvent;

private mapCreated = new AsyncSubject<void>();
private mapLoaded = new AsyncSubject<void>();

constructor(
private zone: NgZone,
@Optional() @Inject(MAPBOX_API_KEY) private readonly MAPBOX_API_KEY: string
) {
this.mapCreated$ = this.mapCreated.asObservable();
this.mapLoaded$ = this.mapLoaded.asObservable();
}

setup(options: SetupMap) {
Expand All @@ -54,10 +52,7 @@ export class MapService {
this.assign(MapboxGl, 'config.customMapboxApiUrl', options.customMapboxApiUrl);
this.createMap(options.mapOptions);
this.hookEvents(options.mapEvents);
options.mapEvents.load.first().subscribe(() => {
this.mapLoaded.next(undefined);
this.mapLoaded.complete();
});
this.mapEvents = options.mapEvents;
this.mapCreated.next(undefined);
this.mapCreated.complete();
return this.mapInstance;
Expand Down Expand Up @@ -132,6 +127,18 @@ export class MapService {
});
}

changeCanvasCursor(cursor: string) {
const canvas = this.mapInstance.getCanvasContainer();
canvas.style.cursor = cursor;
}

queryRenderedFeatures(
pointOrBox?: MapboxGl.PointLike | MapboxGl.PointLike[],
parameters?: { layers?: string[], filter?: any[] }
) {
return this.mapInstance.queryRenderedFeatures(pointOrBox, parameters);
}

panTo(center: MapboxGl.LngLatLike) {
return this.zone.runOutsideAngular(() => {
this.mapInstance.panTo(center);
Expand Down Expand Up @@ -163,20 +170,20 @@ export class MapService {
.forEach((key: keyof MapboxGl.Layer) =>
layer.layerOptions[key] === undefined && delete layer.layerOptions[key]);
this.mapInstance.addLayer(layer.layerOptions, before);
});
this.mapInstance.on('click', layer.layerOptions.id, (evt: MapboxGl.MapMouseEvent) => {
this.zone.run(() => {
layer.layerEvents.click.emit(evt);
this.mapInstance.on('click', layer.layerOptions.id, (evt: MapboxGl.MapMouseEvent) => {
this.zone.run(() => {
layer.layerEvents.click.emit(evt);
});
});
});
this.mapInstance.on('mouseenter', layer.layerOptions.id, (evt: MapboxGl.MapMouseEvent) => {
this.zone.run(() => {
layer.layerEvents.mouseEnter.emit(evt);
this.mapInstance.on('mouseenter', layer.layerOptions.id, (evt: MapboxGl.MapMouseEvent) => {
this.zone.run(() => {
layer.layerEvents.mouseEnter.emit(evt);
});
});
});
this.mapInstance.on('mouseleave', layer.layerOptions.id, (evt: MapboxGl.MapMouseEvent) => {
this.zone.run(() => {
layer.layerEvents.mouseLeave.emit(evt);
this.mapInstance.on('mouseleave', layer.layerOptions.id, (evt: MapboxGl.MapMouseEvent) => {
this.zone.run(() => {
layer.layerEvents.mouseLeave.emit(evt);
});
});
});
}
Expand Down
5 changes: 3 additions & 2 deletions src/app/lib/source/canvas-source.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { CanvasSourceOptions } from 'mapbox-gl';
import { Component, Input, OnDestroy, OnInit, SimpleChanges, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import 'rxjs/add/operator/switchMap';
import { MapService } from '../map/map.service';

@Component({
Expand All @@ -24,7 +25,7 @@ export class CanvasSourceComponent implements OnInit, OnDestroy, OnChanges, Canv
) { }

ngOnInit() {
this.MapService.mapLoaded$.subscribe(() => {
this.MapService.mapCreated$.switchMap(() => this.MapService.mapEvents.load).first().subscribe(() => {
const source = {
type: 'canvas',
coordinates: this.coordinates,
Expand Down
70 changes: 70 additions & 0 deletions src/app/lib/source/geojson/draggable.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { FeatureComponent } from './feature.component';
import { Directive, Host, Input, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { merge } from 'rxjs/observable/merge';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { LayerComponent } from '../../layer/layer.component';
import { MapService } from '../../map/map.service';

@Directive({
selector: '[mglDraggable]'
})
export class DraggableDirective implements OnInit, OnDestroy {
// tslint:disable-next-line:no-input-rename
@Input('mglDraggable') source: LayerComponent;

private destroyed$: ReplaySubject<void> = new ReplaySubject(1);

constructor(
private MapService: MapService,
@Host() private FeatureComponent: FeatureComponent
) { }

ngOnInit() {
if (this.FeatureComponent.geometry.type !== 'Point') {
throw new Error('mglDraggable only support point feature');
}
this.MapService.mapCreated$.subscribe(() => {
this.source.mouseEnter
.takeUntil(this.destroyed$)
.subscribe((evt) => {
const feature: GeoJSON.Feature<any> = this.MapService.queryRenderedFeatures(
evt.point,
{
layers: [this.source.id],
filter: [
'all',
['==', '$type', 'Point'],
['==', '$id', this.FeatureComponent.id]
]
}
)[0];
if (!feature) {
return;
}
this.MapService.changeCanvasCursor('move');
this.MapService.updateDragPan(false);
this.MapService.mapEvents.mouseDown
.takeUntil(merge(this.destroyed$, this.source.mouseLeave))
.subscribe(() => {
this.MapService.mapEvents.mouseMove
.takeUntil(merge(this.destroyed$, this.MapService.mapEvents.mouseUp))
.subscribe((evt) => {
this.FeatureComponent.updateCoordinates([evt.lngLat.lng, evt.lngLat.lat]);
});
});
});
this.source.mouseLeave
.takeUntil(this.destroyed$)
.subscribe(() => {
this.MapService.changeCanvasCursor('');
this.MapService.updateDragPan(true);
});
});
}

ngOnDestroy() {
this.destroyed$.next(undefined);
this.destroyed$.complete();
}
}
Loading

0 comments on commit 1166346

Please sign in to comment.