Skip to content

Imagery create new map provider

Tzahi Levi edited this page May 7, 2019 · 7 revisions

let's create a new map provider that we can supply to our imagery module.

prerequisites

  • you need an app as describe here
    or you can git clone https://github.com/AnSyn/imagery-implementation.git
  • install cesium npm i cesium

Create our new provider

we create a new map provider for Cesium with OSM source provider

Step 1

we need to add Cesium to our build.
go to your angluar.json file add do to following:

  • add to projects.YOUR_PROJECT_NAME.architect.build.options.assets array this object
{
    "glob": "**/*",
    "input": "node_modules/cesium/Build/Cesium",
     "output": "/assets/Cesium"
},
  • add to projects.YOUR_PROJECT_NAME.architect.build.options.scripts the string "node_modules/cesium/Build/Cesium/Cesium.js"

Step 2

Create a new directory called cesium

Step 3

create a new file cesium/cesium-OSM-source-proveider.ts and copy the following code to it:

import { BaseMapSourceProvider, ImageryMapSource, IBaseImageryMapConstructor, IMapSettings } from '@ansyn/imagery';
import {CesiumMap} from './cesium-map';
declare const Cesium: any;
@ImageryMapSource({
  supported: [CesiumMap],
  sourceType: 'OSM'
})
export class CesiumOsmSourceProvider extends BaseMapSourceProvider {

  protected create(metaData: any): Promise<any> {
    const cesiumOsmLayer = Cesium.createOpenStreetMapImageryProvider();
    return Promise.resolve(cesiumOsmLayer);
  }

}

Step 4

create a new file cesium/cesium-map.ts and copy the following code to it:

import {BaseImageryMap, ImageryMap, ImageryMapExtent, ImageryMapPosition} from '@ansyn/imagery';
import {feature, featureCollection, geometry} from '@turf/turf';
import {GeoJsonObject, Point, Polygon} from 'geojson';
import {Observable, of} from 'rxjs';
import {mergeMap} from 'rxjs/operators';

declare const Cesium: any;
Cesium.buildModuleUrl.setBaseUrl('assets/Cesium/');
export const toDegrees = (radians: number): number => {
  return radians * 180 / Math.PI;
};

@ImageryMap({
  mapType: 'CesiumMap',
  deps: []
})
export class CesiumMap extends BaseImageryMap<any> {
  element: HTMLElement;
  mapObject: any;

  initMap(element: HTMLElement, shadowNorthElement: HTMLElement, shadowDoubleBufferElement: HTMLElement, layer?: any, position?: ImageryMapPosition): Observable<boolean> {
    this.element = element;
    return this.resetView(layer, position);
  }

  resetView(layer: any, position: ImageryMapPosition, extent?: ImageryMapExtent): Observable<boolean> {
    if (!this.mapObject) {
      return this.createMapObject(layer).pipe(
        mergeMap((isReady) => {
          return this.setOrFit(position, extent);
        }));
    }
    this.setOrFit(position, extent);
  }

  getCenter(): Observable<Point> {
    const viewer = this.mapObject;
    const windowPosition = new Cesium.Cartesian2(viewer.container.clientWidth / 2, viewer.container.clientHeight / 2);
    const pickRay = viewer.scene.camera.getPickRay(windowPosition);
    const pickPosition = viewer.scene.globe.pick(pickRay, viewer.scene);
    const pickPositionCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(pickPosition);
    const long = toDegrees(pickPositionCartographic.longitude);
    const lat = toDegrees(pickPositionCartographic.latitude);
    const point: Point = {
      type: 'Point',
      coordinates: [long, lat]
    };
    return of(point);
  }

  setCenter(center: Point, animation: boolean): Observable<boolean> {
    const currentPosition = this.mapObject.camera.positionCartographic;
    const extentFeature = feature(center);
    const collection: any = featureCollection([extentFeature]);
    const geoJsonCenter = collection.features[0].geometry.coordinates;
    this.mapObject.camera.flyTo({
      destination: Cesium.Cartesian3.fromDegrees(geoJsonCenter[0], geoJsonCenter[1], currentPosition.height)
    });
    return of(true);
  }

  getRotation(): number {
    return NaN;
  }

  createMapObject(layer: any): Observable<boolean> {
    if (this.mapObject) {
      this.internalDestroyCesium();
    }
    const viewer = new Cesium.Viewer(this.element, {
      imageryLayers: layer,
      baseLayerPicker: false
    });
    viewer.scene.globe.baseColor = Cesium.Color.BLACK;
    this.mapObject = viewer;
    return of(true);
  }

  setOrFit(position: ImageryMapPosition, extent?: ImageryMapExtent) {
    if (extent) {
      return this.fitToExtent(extent);
    }
    return this.setPosition(position);
  }

  _imageToGround({x, y}: { x: number, y: number }) {
    const position = this.mapObject.camera.getPickRay({x, y});
    const cartesian = this.mapObject.scene.globe.pick(position, this.mapObject.scene);
    if (cartesian) {
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      const longitude = Cesium.Math.toDegrees(cartographic.longitude);
      const latitude = Cesium.Math.toDegrees(cartographic.latitude);
      return [longitude, latitude];
    } else {
      throw new Error('Empty Point');
    }
  }

  internalSetPosition(extentPolygon: Polygon): Observable<boolean> {
    const extentFeature = feature(extentPolygon);
    const collection: any = featureCollection([extentFeature]);
    const geoJsonExtent = collection.features[0].geometry;
    const rec = [...geoJsonExtent.coordinates[0][0], ...geoJsonExtent.coordinates[0][2]];
    this.mapObject.camera.setView({
      destination: Cesium.Rectangle.fromDegrees(...rec)
    });
    return of(true);
  }

  internalDestroyCesium() {
    if (this.mapObject) {
      this.mapObject.destroy();
    }
  }

  public dispose() {
    this.internalDestroyCesium();
  }

  setPosition(position: ImageryMapPosition): Observable<boolean> {
    const {extentPolygon} = position;
    return this.internalSetPosition(extentPolygon);
  }

  getPosition(): Observable<ImageryMapPosition> {
    try {
      const {height, width} = this.mapObject.canvas;
      const topLeft = this._imageToGround({x: 0, y: 0});
      const topRight = this._imageToGround({x: width, y: 0});
      const bottomRight = this._imageToGround({x: width, y: height});
      const bottomLeft = this._imageToGround({x: 0, y: height});
      const extentPolygon = <Polygon>geometry('Polygon', [[topLeft, topRight, bottomRight, bottomLeft, topLeft]]);
      return of({extentPolygon});
    } catch (error) {
      return of(null);
    }
  }

  addGeojsonLayer(data: GeoJsonObject): any {
  }

  addLayer(layer: any): void {
  }

  addLayerIfNotExist(layer: any): any {
  }

  getLayers(): any[] {
    return [];
  }

  removeLayer(layer: any): void {
  }

  setRotation(rotation: number): void {
  }

  toggleGroup(groupName: string, newState: boolean): any {
  }

  updateSize(): void {
  }
}

Step 5

replace the provider inside ImageryModule in app.module.ts

...
ImageryModule.provide({
      maps: [CesiumMap],
      plugins: [],
      mapSourceProviders: [CesiumOsmSourceProvider]
    }),
  ],
  providers: [
    {
      provide: MAP_PROVIDERS_CONFIG,
      useValue: {
        CesiumMap: {
          defaultMapSource: 'OSM'
        }
      }
    },
    {
      provide: MAP_SOURCE_PROVIDERS_CONFIG,
      useValue: {
        OSM: { }
      }
    }
  ]
...

Step 6

change our Settings.mapType prop in app.component.ts to CesiumMap

you should be see something like this