Search code examples
angulararcgisarcgis-js-apiangular10esri-loader

geographicToWebMercator and Projected polygon in Angular ArcGIS esri-loader


How to use 'geographicToWebMercator' in Angular 10 to draw a polygon with rings values. I am referring the example provided In the documentation, https://developers.arcgis.com/documentation/core-concepts/features-and-geometries/#polygons and trying to use the functionalities in Angular with ArcGIS esri-loader. Here is the documentation I am referring. https://developers.arcgis.com/javascript/latest/guide/angular/

I have tried to get the projected polygon points. But returns wrong values.

import {
  Component,
  OnInit,
  ViewChild,
  ElementRef,
  Input,
  Output,
  EventEmitter,
  OnDestroy,
} from '@angular/core';
import { loadModules } from 'esri-loader';
import esri = __esri; // Esri TypeScript Types
import { empty } from 'rxjs';

@Component({
  selector: 'app-esri-map',
  templateUrl: './esri-map.component.html',
  styleUrls: ['./esri-map.component.css'],
})
export class EsriMapComponent implements OnInit, OnDestroy {
  @Output() mapLoadedEvent = new EventEmitter<boolean>();

  @ViewChild('mapViewNode', { static: true }) private mapViewEl: ElementRef;

  /**
   * _zoom sets map zoom
   * _center sets map center
   * _basemap sets type of map
   * _loaded provides map loaded status
   */
  private _zoom = 20;

  private _basemap = 'hybrid';
  private _loaded = false;
  private _view: esri.MapView = null;
  private _nextBasemap = 'streets';

  get mapLoaded(): boolean {
    return this._loaded;
  }

  @Input()
  set zoom(zoom: number) {
    this._zoom = zoom;
  }

  get zoom(): number {
    return this._zoom;
  }

  @Input()
  set basemap(basemap: string) {
    this._basemap = basemap;
  }

  get basemap(): string {
    return this._basemap;
  }

  @Input()
  set nextBasemap(nextBasemap: string) {
    this._nextBasemap = nextBasemap;
  }

  get nextBasemap(): string {
    return this._nextBasemap;
  }

  constructor() {}

  async getProjectedpolygon(params: any) {
    const [GeometryService] = await loadModules(['esri/tasks/GeometryService']);

    console.log(`input polygon: ${JSON.stringify(params.toJSON())}`);

    const geomSer = new GeometryService(
      'http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer'
    );
    geomSer.project(params).then(function (result) {
      const projectedPoly = result[0];

      if (!projectedPoly) {
        return;
      }

      console.log(
        `Projected Polygon: ${JSON.stringify(projectedPoly.toJSON())}`
      );
      // return projectedPoly;
    });
  }

  async initializeMap() {
    try {
      // Load the modules for the ArcGIS API for JavaScript
      const [
        EsriMap,
        EsriMapView,
        FeatureLayer,
        BasemapToggle,
        BasemapGallery,
        webMercatorUtils,
        Polygon,
        Graphic,
        SimpleFillSymbol,
        GeometryService,
        ProjectParameters,
        SpatialReference,
      ] = await loadModules([
        'esri/Map',
        'esri/views/MapView',
        'esri/layers/FeatureLayer',
        'esri/widgets/BasemapToggle',
        'esri/widgets/BasemapGallery',
        'esri/geometry/support/webMercatorUtils',
        'esri/geometry/Polygon',
        'esri/Graphic',
        'esri/symbols/SimpleFillSymbol',
        'esri/tasks/GeometryService',
        'esri/tasks/support/ProjectParameters',
        'esri/geometry/SpatialReference',
      ]);

      const poly = Polygon.fromJSON({
        rings: [
          [
            [2755899.9999897182, 542357.7692040652],
            [2755880.0069194734, 542356.78593830764],
            [2755881.7250918895, 542323.36310489476],
            [2755825.535899803, 542319.92347922921],
            [2755846.6395321339, 541826.4208573103],
            [2755848.0673508048, 541783.2063768059],
            [2755849.6063897163, 541736.63199488819],
            [2755853.2533640563, 541637.62366272509],
            [2755853.747457549, 541624.21063180268],
            [2755844.9098768085, 541604.88324263692],
            [2755840.0076556355, 541604.63127464056],
            [2755840.858703807, 541584.26386131346],
            [2755696.7467873096, 541576.85377115011],
            [2755695.3304515481, 541630.02623698115],
            [2755695.2727088928, 541632.78771439195],
            [2755693.2822273076, 541727.9331934005],
            [2755692.2743553072, 541778.09352613986],
            [2755691.192008391, 541827.82735055685],
            [2755685.6582268029, 542074.87574097514],
            [2755683.43447797, 542124.91665139794],
            [2755676.4262898862, 542274.51674589515],
            [2755689.3068415523, 542275.07809647918],
            [2755688.1060565561, 542301.07771639526],
            [2755718.8845383078, 542345.19869114459],
            [2755808.4171676338, 542473.54521922767],
            [2755824.3219914734, 542498.22528797388],
            [2755834.2757117152, 542498.80697973073],
            [2755854.8573634773, 542526.09924797714],
            [2755868.594868809, 542578.80485123396],
            [2755868.6171784699, 542578.83765955269],
            [2755899.9999897182, 542624.7959009707],
            [2755920.8641212136, 542655.35030181706],
            [2756017.1844666302, 542660.17968848348],
            [2756013.8029117137, 542710.30819714069],
            [2756010.1959635466, 542763.77626605332],
            [2755899.9999897182, 542757.74018889666],
            [2755858.8612924665, 542755.48691256344],
            [2755856.16346322, 542804.39462314546],
            [2755899.9999897182, 542806.61312264204],
            [2756006.9410488009, 542812.02584147453],
            [2756021.3320961446, 542812.82603672147],
            [2756073.078383714, 542815.70299947262],
            [2756156.5181774646, 542820.34242589772],
            [2756161.6395583004, 542717.00962731242],
            [2756171.2474787235, 542523.15535622835],
            [2756053.1381348819, 542512.12617881596],
            [2756041.1913083941, 542508.49495247006],
            [2756026.1532807201, 542503.48676039279],
            [2756011.1966177225, 542498.69838413596],
            [2755984.824951306, 542480.93496823311],
            [2755968.4503121376, 542464.29556581378],
            [2755964.7866055518, 542457.93402998149],
            [2755936.9047715515, 542405.95185047388],
            [2755918.0816464722, 542358.65863797069],
            [2755899.9999897182, 542357.7692040652],
          ],
        ],
        spatialReference: {
          wkid: 102704,
        },
      });

      const geomSer = new GeometryService(
        'http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer'
      );

      const outSpatialReference = new SpatialReference({ wkid: 102704 });

      const params = new ProjectParameters({
        geometries: [poly],
        outSpatialReference,
      });

      // let geomser = await geomSer.project(params);
      let geomser = await Promise.all([this.getProjectedpolygon(params)]);
      console.log(geomser);

      // Configure the Map
      const mapProperties: esri.MapProperties = {
        basemap: this._basemap,
      };

      const map: esri.Map = new EsriMap(mapProperties);

      // Initialize the MapView
      const mapViewProperties: esri.MapViewProperties = {
        container: this.mapViewEl.nativeElement,
        // center: this._center,
        zoom: this._zoom,
        map: map,
        extent: webMercatorUtils.geographicToWebMercator(
          poly.extent.clone().expand(3)
        ),
      };

      this._view = new EsriMapView(mapViewProperties);

      // this._view.graphics.add(graphic);

      await this._view.when();
      return this._view;
    } catch (error) {
      console.log('EsriLoader: ', error);
    }
  }

  ngOnInit() {
    this.initializeMap().then((mapView) => {
      console.log('mapView ready: ', this._view.ready);
      this._loaded = this._view.ready;
      this.mapLoadedEvent.emit(true);
    });
  }

  ngOnDestroy() {
    if (this._view) {
      this._view.container = null;
    }
  }
}

/Updated code*****/

  import {
  Component,
  OnInit,
  ViewChild,
  ElementRef,
  Input,
  Output,
  EventEmitter,
  OnDestroy,
} from '@angular/core';
import { loadModules } from 'esri-loader';
import esri = __esri; // Esri TypeScript Types
import { empty } from 'rxjs';
import { __asyncValues } from 'tslib';

@Component({
  selector: 'app-esri-map',
  templateUrl: './esri-map.component.html',
  styleUrls: ['./esri-map.component.css'],
})
export class EsriMapComponent implements OnInit, OnDestroy {
  // The <div> where we will place the map
  @ViewChild('mapViewNode', { static: true }) private mapViewEl: ElementRef;
  // view: any;

  // to keep loaded esri modules
  esriModules = {
    geometry: {
      Polygon: null,
      SpatialReference: null,
      support: { webMercatorUtils: null },
    },
    tasks: {
      GeometryService: null,
      support: { ProjectParameters: null },
    },
  };
  private _zoom = 20;
  private _center: Array<number> = [-95.937187, 41.258652];
  private _basemap = 'hybrid';
  private _loaded = false;
  private _view: esri.MapView = null;
  private _nextBasemap = 'streets';
  public _selectedLayer: Array<string>;

  public onLayerChange(val: Array<string>) {
    this._selectedLayer = val;
    this.initializeMap();
  }

  constructor() {}

  async initializeMap() {
    try {
      // Load the modules for the ArcGIS API for JavaScript
      const [
        EsriMap,
        EsriMapView,
        Polygon,
        SpatialReference,
        webMercatorUtils,
        GeometryService,
        ProjectParameters,
      ] = await loadModules([
        'esri/Map',
        'esri/views/MapView',
        'esri/geometry/Polygon',
        'esri/geometry/SpatialReference',
        'esri/geometry/support/webMercatorUtils',
        'esri/tasks/GeometryService',
        'esri/tasks/support/ProjectParameters',
      ]);

      // save the modules on a property for later
      this.esriModules.geometry.Polygon = Polygon;
      this.esriModules.geometry.SpatialReference = SpatialReference;
      this.esriModules.geometry.support.webMercatorUtils = webMercatorUtils;
      this.esriModules.tasks.GeometryService = GeometryService;
      this.esriModules.tasks.support.ProjectParameters = ProjectParameters;

      // Configure the Map
      const mapProperties: esri.MapProperties = {
        basemap: this._basemap,
      };

      const map: esri.Map = new EsriMap(mapProperties);

      // Initialize the MapView
      const mapViewProperties: esri.MapViewProperties = {
        container: this.mapViewEl.nativeElement,
        center: this._center,
        zoom: this._zoom,
        map: map,
      };

      this._view = new EsriMapView(mapViewProperties);

      // const map = new Map(mapProperties);

      // Initialize the MapView
      // const mapViewProperties = {
      //   container: this.mapViewEl.nativeElement,
      //   map,
      // };

      // this.view = new MapView(mapViewProperties);
      // this._view = new EsriMapView(mapViewProperties);

      await this._view.when();
      return this._view;
    } catch (error) {
      console.error('EsriLoader: ', error);
    }
  }

  // be carefull does not handle point geometries
  // point geometry extent is null
  public zoomToGeometry(geom) {
    console.log('in zoomToGeometry');

    console.log(`Original Geometry: ${JSON.stringify(geom.toJSON())}`);

    const geomSer = new this.esriModules.tasks.GeometryService(
      'http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer'
    );

    const outSpatialReference = new this.esriModules.geometry.SpatialReference({
      wkid: 102100,
    });

    const params = new this.esriModules.tasks.support.ProjectParameters({
      geometries: [geom],
      outSpatialReference,
    });

    geomSer.project(params).then(function (result) {
      const projectedGeom = result[0];

      if (!projectedGeom) {
        console.log('projection geom');

        return;
      }

      console.log(
        `Projected Geometry: ${JSON.stringify(projectedGeom.toJSON())}`
      );

      this._view.extent = projectedGeom.extent.clone().expand(3);
    });
  }

  ngOnInit() {
    this.initializeMap();

    const geom = new this.esriModules.geometry.Polygon({
      spatialReference: {
        wkid: 102704,
      },
      rings: [
        [
          [2744913.4668447226, 541568.06113781035],
          [2744917.4038447142, 541499.65215389431],
          [2744864.2454864681, 541496.82210706174],
          [2744813.6648789644, 541494.12952713668],
          [2744810.2104895562, 541563.64283956587],
          [2744860.4905727208, 541565.79441006482],
          [2744913.4668447226, 541568.06113781035],
        ],
      ],
    });

    this.zoomToGeometry(geom);
  }

  ngOnDestroy() {
    if (this._view) {
      // destroy the map view
      this._view.container = null;
    }
  }
}

Solution

  • The first thing is for you to get a basic example of esri-loader and angular working together. If you are not there yet, take a look to the docs there are a couple of simple examples to start with in the guide section (ArcGIS Guide - Angular).

    Now, that you have a basic example, you just need to load webMercatorUtils module and use it. Using the example from ArcGIS Guide as a base, you can do something like this,

    import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from "@angular/core";
    import { loadModules } from "esri-loader";
    
    @Component({
      selector: "app-esri-map",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.scss"]
    })
    export class MapComponent implements OnInit, OnDestroy {
      // The <div> where we will place the map
      @ViewChild("mapViewNode", { static: true }) private mapViewEl: ElementRef;
      view: any;
    
      constructor() {}
    
      async initializeMap() {
        try {
          // Load the modules for the ArcGIS API for JavaScript
          const [Map, MapView, webMercatorUtils, Polygon] = await loadModules(["esri/Map", "esri/views/MapView", "esri/geometry/support/webMercatorUtils", "esri/geometry/Polygon"]);
    
          // now do your thing with the modules
          // or save the modules on a property for later
          
          // btw, this json is from the link of the question
          // I am gonna use it for the initial extent
          const poly = Polygon.fromJSON({
            "rings":[
              [
                [ -118.38516, 34.01270 ],
                [ -118.38827, 34.01489 ],
                [ -118.38813, 34.01602 ],
                [ -118.38797, 34.01648 ],
                [ -118.38760, 34.01712 ],
                [ -118.38733, 34.01696 ],
                [ -118.38696, 34.01749 ],
                [ -118.38662, 34.01789 ],
                [ -118.38689, 34.01805 ],
                [ -118.38683, 34.01812 ],
                [ -118.38295, 34.01592 ],
                [ -118.38516, 34.01270 ]
              ],
              [
                [ -118.38661, 34.01486 ],
                [ -118.38634, 34.01498 ],
                [ -118.38652, 34.01563 ],
                [ -118.38670, 34.01559 ],
                [ -118.38679, 34.01595 ],
                [ -118.38699, 34.01591 ],
                [ -118.38707, 34.01507 ],
                [ -118.38661, 34.01486 ]
              ]
            ],
            "spatialReference": {
              "wkid": 4326
            }
          });
          const extent = webMercatorUtils.geographicToWebMercator(
            // you need to clone the polygon extent because is readonly
            // besides why would you change it
            poly.extent.clone().expand(3)
          );
    
          // Configure the Map
          const mapProperties = {
            basemap: "streets-vector"
          };
    
          const map = new Map(mapProperties);
    
          // Initialize the MapView
          const mapViewProperties = {
            container: this.mapViewEl.nativeElement,
            extent, // use extent
            map
          };
    
          this.view = new MapView(mapViewProperties);
          await this.view.when(); // wait for map to load
          return this.view;
        } catch (error) {
          console.error("EsriLoader: ", error);
        }
      }
    
      ngOnInit() {
        this.initializeMap();
      }
    
      ngOnDestroy() {
        if (this.view) {
          // destroy the map view
          this.view.container = null;
        }
      }
    }
    

    UPDATE

    Here you have the previous example, with some changes to help you understand or handle your case. You will see that I keep the loaded modules in a property of the component. Then I just use it later.

    import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from "@angular/core";
    import { loadModules } from "esri-loader";
    
    @Component({
      selector: 'app-esri-map',
      templateUrl: './esri-map.component.html',
      styleUrls: ['./esri-map.component.scss']
    })
    export class EsriMapComponent implements OnInit, OnDestroy {
    
      @ViewChild("mapViewNode", { static: true }) private mapViewEl: ElementRef;
      view: any;
    
      // to keep loaded esri modules
      esriModules = {
        geometry: {
          Polygon: null,
          SpatialReference: null,
          support: { webMercatorUtils: null }
        },
        tasks: {
          GeometryService: null,
          support: { ProjectParameters: null }
        }
      };
    
      constructor() {}
    
      async initializeMap() {
        try {
          // Load the modules for the ArcGIS API for JavaScript
          const [
            Map,
            MapView,
            Polygon,
            SpatialReference,
            webMercatorUtils,
            GeometryService,
            ProjectParameters
          ] = await loadModules([
            "esri/Map",
            "esri/views/MapView",
            "esri/geometry/Polygon",
            "esri/geometry/SpatialReference",
            "esri/geometry/support/webMercatorUtils",
            "esri/tasks/GeometryService",
            "esri/tasks/support/ProjectParameters"
          ]);
    
          // save the modules on a property for later
          this.esriModules.geometry.Polygon = Polygon;
          this.esriModules.geometry.SpatialReference = SpatialReference;
          this.esriModules.geometry.support.webMercatorUtils = webMercatorUtils;
          this.esriModules.tasks.GeometryService = GeometryService;
          this.esriModules.tasks.support.ProjectParameters = ProjectParameters;
    
          // Configure the Map
          const mapProperties = {
            basemap: "streets"
          };
    
          const map = new Map(mapProperties);
    
          // Initialize the MapView
          const mapViewProperties = {
            container: this.mapViewEl.nativeElement,
            map
          };
    
          this.view = new MapView(mapViewProperties);
          await this.view.when(); // wait for map to load
          return this.view;
        } catch (error) {
          console.error("EsriLoader: ", error);
        }
      }
    
      // be carefull does not handle point geometries
      // point geometry extent is null
      zoomToGeometry(geom) {
        console.log(`Original Geometry: ${JSON.stringify(geom.toJSON())}`);
    
        const geomSer = new this.esriModules.tasks.GeometryService(
          "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer"
        );
    
        const outSpatialReference = new this.esriModules.geometry.SpatialReference({ wkid: 102100 });
    
        const params = new this.esriModules.tasks.support.ProjectParameters({
          geometries: [geom],
          outSpatialReference
        });
    
        const self = this;
    
        geomSer.project(params).then(function(result) {
          const projectedGeom = result[0];
    
          if (!projectedGeom) {
            return;
          }
    
          console.log(`Projected Geometry: ${JSON.stringify(projectedGeom.toJSON())}`);
    
          self.view.extent = projectedGeom.extent.clone().expand(3);
    
        });
      }
    
      ngOnInit() {
        this.initializeMap().then(_ => {
          // The map has been initialized
          console.log("mapView ready: ", this.view.ready);
          const geom = new this.esriModules.geometry.Polygon({
            spatialReference: {
              wkid: 102704
            },
            rings: [
              [
                [
                  2744913.4668447226,
                  541568.06113781035
                ],
                [
                  2744917.4038447142,
                  541499.65215389431
                ],
                [
                  2744864.2454864681,
                  541496.82210706174
                ],
                [
                  2744813.6648789644,
                  541494.12952713668
                ],
                [
                  2744810.2104895562,
                  541563.64283956587
                ],
                [
                  2744860.4905727208,
                  541565.79441006482
                ],
                [
                  2744913.4668447226,
                  541568.06113781035
                ]
              ]
            ]
          });
    
          this.zoomToGeometry(geom);
        });
      }
    
      ngOnDestroy() {
        if (this.view) {
          // destroy the map view
          this.view.container = null;
        }
      }
    
    }
    
    

    In the code I keep the loaded modules in the property esriModules. To keep track of the modules names, I decided to use to structure the object values. For example, the Polygon class of the geometry module, path esri/geometry/Polygon, is access through this.esriModules.geometry.Polygon.