Ionic, ESRI JavaScript API, and Geometry Services – Brian Ferry's Website
Brian Ferry

For this post I will be going over an example application using the Ionic Framework, Angular 5, TypeScript, and ESRI JavaScript API.

Technology used for this application:

  • ionic-angular: 3.9.5
  • angular: 5.2.11
  • esri-loader: 2.10.0
  • typescript: 2.6.2

This project can be found at https://github.com/brianferry/greenway-app

Throughout my career I've used multiple versions of the ESRI JavaScript API as well as multiple JavaScript frameworks so it seems natural that as I progress I would want to dive into using both Ionic (A Mobile UI Framework that I will be using with Angular) and ESRI's latest JavaScript iteration.

For this application I wanted a simple use-case which is the user typing in an address, getting a list of results back and being able to click that result to zoom into the result.

This projects UI is in no way finished but more of a proof of concept on how the frameworks work together in a pseudo real-world example.

For this, I have decided to use the Open GIS Data provided by Wake County at https://data-wake.opendata.arcgis.com/datasets/ as well as the Geometry Service provided by ESRI at http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer.

I will be breaking out my project into three parts in this post.

  1. Setting up the map in the Ionic Framework
  2. Setting up the search function to work with the Wake County data
  3. Using the ESRI Geometry service to get the distance.

Setting up the ESRI Application within the Ionic Framework

To begin, type the following into your node command prompt

npm i esri-loader @types/arcgis-js-api

Which will install the two packages located here

After this we want to set the map to actually show on the screen we're displaying. For this I created a new component named component-map.ts which can be seen here: https://github.com/brianferry/greenway-app/tree/master/src/components/greenway-map

HTML

<div id="map" #map></div>

TypeScript

import { Component, ViewChild, ElementRef, OnInit } from '@angular/core';
import { Platform } from 'ionic-angular';
import { loadModules } from 'esri-loader';

@Component({
  selector: 'greenway-map',
  templateUrl: 'greenway-map.html'
})
export class GreenwayMapComponent implements OnInit {

  @ViewChild('map') mapEl: ElementRef;

  //Sets up the maps default constructor.  
  async getGeo() {
    await this._platform.ready();

    const [
            Map
            , MapView
          ]: any
      = await loadModules([
        'esri/Map',
        'esri/views/MapView',
      ]);

    let map = new Map({
      basemap: 'topo'
    });

    // Inflate and display the map
    let mapView = new MapView({
      // create the map view at the DOM element in this component
      container: this.mapEl.nativeElement,
      center: [-78.6382, 35.7796],
      zoom: 12,
      map: map
    });
  }

  constructor(public _platform: Platform) {
  }

  //Initialize the map
  ngOnInit() {
    this.getGeo();
  }
}

CSS

#map{
    height: 500px;
    width: 100%;
}

 

This is the most basic application for getting your map on the screen like I have it.  For this I initialize the map after the page as loaded, created a new map object with the basemap of topo meaning topography (more information here).  After this I create a MapView which is a 2D map that will be displayed on the screen.  (If I wanted a 3D map I would use SceneView).

Include the css in your css / scss file and that's it!  You should now see a map appear on your screen.

Your app should now have a map that looks something like this

Raleigh Map with Greenways Highlighted

Setting up the Search function

Setting up the search function requires that we bring in the Search Widget and assign it to a component.  You can either attach it to the MapView itself with the default stylings, or you can put it in it's own HTML component itself to have it outside of the map on the page, which is what is done below.

HTML

<div id="address" #address></div>

CSS

#address{
    display:block;
    text-align:left;
    width:100%;
    margin:0;
    padding: 14px;
    border: 1px solid #ccc;
    margin-bottom:20px;
}

@media screen and (max-width: 600px){
    #address{
        display:block;
        text-align:left;
        width:100%;
        margin:0;
        padding: 14px;
        border: 1px solid #ccc;
    }
}

#address-suggest-menu{
    width: 100%;
    display: inline-block;
}

.esri-search__suggestions-list{
    // position: absolute;
}

.esri-search__suggestions-list > li{
    padding: 1em;
}

TypeScript

const [
        Search
          ]: any
      = await loadModules([
        'esri/widgets/Search'
      ]);

var search = new Search({
      view: mapView, //Shown in previous example
      container: this.addressElement.nativeElement //@ViewChild('address') addressElement: ElementRef;
    });

From this code we can see that the Search is still tied to the MapView (view: mapView), but also contains a reference to the specific HTML reference on the page itself (container: this.addressElement.nativeElement). 

I also set a function call on the search results coming back which will be how our application parses through the results and gets information back.  

search.on("select-result", ((evt) => this.getResults(this, evt)));

 this.getResults = function (self: any, evt: any) {
      this.loading = true;
      this.queried = true;
      this.features = Array<any>();
      let x = evt.result.feature.geometry.x;
      let y = evt.result.feature.geometry.y;

      this._http.get(this.SelectedURL + `/query?outFields=*&geometry=${x}%2C${y}&geometryType=esriGeometryPoint&inSR=${esriSettings.wkid}&returnGeometry=true&returnCurves=false&spatialRel=esriSpatialRelIntersects&distance=${esriSettings.distanceExtentUnit}&units=${esriSettings.measurement}&outSR=${esriSettings.wkid}&f=pjson`)
        .subscribe((data: any) => {
          if (data !== undefined && Object.keys(data).length !== 0 && data.features !== undefined) {
            let _features = data.features.map((response: Array<any>) => response);
            _features.forEach((element: any) => {
              if (this.features.findIndex(a => a.attributes.TRAIL_NAME === element.attributes.TRAIL_NAME) <= -1) {
                this.features.push(element);
              }
            });

            let geoService = new GeometryService(esriSettings.geoServiceURL);
            this.geoDistance(geoService, DistanceParameters, evt);
          }
        });
    }

In this function below I loop through the featured results and calculate the distance using the ESRI distance calculator mentioned above.

I also have some JavaScript in there to sort the records using the sortBy() function in the code.

The end result looks something like this:

Wake County Search Results From Address

However, we still haven't gone over the distance piece.

Using the ESRI Geometery Service to Calculate Distance

The ESRI Geometry Service is a free service available for development (non-production) purposes.  For our purposes we pass in the geometry from the Wake County GIS Service as well as the geometry from the address put into the system and calculate the distance in miles.  We can find a full list of supported measurements here.

The code for this looks like this

geoDistance = function (geometryService: any, DistanceParameters: any, evt: any){
    this.features.forEach(element => {
      let distParam = new DistanceParameters();
      distParam.geometry1 = evt.result.feature.geometry;
      distParam.geometry2 = element.geometry;
      distParam.geodesic = true;
      distParam.distanceUnit = esriSettings.distanceSearchUnit;

      
      geometryService.distance(distParam).then(function (calcDistance) {
        element.distance = calcDistance.toPrecision(2);
      });
    });

    this.loading = false;
  }

This takes in the geometryService we're using, the ESRI Distance Parameter object, as well as the address we input as the 'evt' variable.

We then go through each of the features we returned from our service and loop through for the calculation from the service in miles. 

The distance unit for this application can be set here

// src/config/esriConfig.js

const esriSettings = {
    wkid: 102100,
    measurement: "esriSRUnit_Meter",
    distanceExtentUnit: 10000,
    distanceSearchUnit: "miles",
    geoServiceURL: "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Utilities/Geometry/GeometryServer"
}

And that's it!  To show the map, take in an address, show nearby results and calculate distance in less than 200 lines of JavaScript!  Wow!

For more API examples, check out My GiantBomb API Tutorial!

For a PHP example, check out My WordPress post!