-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



giovedì 31 luglio 2014

WebSocket con StreamLayer

L'evoluzione delle applicazioni web ha anche determinato una crescente richiesta di comunicazioni in tempo reale: basti pensare ad applicazioni di chat, aggiornamenti in tempo reale, giochi online ecc. Il metodo più semplice fino ad ora utilizzato è quello del polling: ad intervalli prestabiliti il client chiede e il server risponde. Lo sviluppatore tramite script invia la richiesta e mediante la risposta del server verifica o meno la presenza delle informazioni richieste; la latenza però potrebbe non essere accettabile. La richiesta può essere tenuta anche più a lungo mantenendo il collegamento con il server (long polling). Questi ed altri metodi però presentano aspetti negativi quali l'inefficienza e la complessità.
Il protocollo WebSocket risolve questi problemi perchè viene mantenuta una connessione TCP persistente, bidirezionale, full-duplex assicurata da un sistema di handshaking client-key ed un modello basato sull'origine; inoltre la trasmissione è mascherata per evitare attacchi. Per maggiori dettagli vedere The WebSocket protocol e WebSocket API.
Il framework 4.5 NET fornisce un'implementazione gestita del protocollo WebSocket  mentre i moderni browser come Chrome, Firefox, Safari, Opera e IE10 supportano la specifica.
Nativamente è supportato da Windows Server 2012 e Windows 8 con IIS8 e IIS8 Express. Con altre versioni di Windows si potrebbe utilizzare SignalR e Socket.IO che hanno anche il grande vantaggio di supportare strategie di fallback (ad esempio browser client che non supportano WebSocket).
Per installare WebSocket su Windows Server 2012:
- Aprire Server Manager;
- cliccare su Add Roles and Features;
- selezionare Role-based or Feature-based Installation e poi cliccare su Next;
- selezionare il server (il server locale è selezionato di default) e poi cliccare su Next;
- espandere Web Server (IIS) in Roles, poi espandere Web Server e poi espandere Application Development;
- selezionare WebSocket Protocol e poi cliccare su Next;
- se non sono necessarie funzionalità aggiuntive cliccare su Next;
- cliccare su Install;
- quando l'installazione è completata chiudere il wizard.



A questo punto l'IIS è abilitato a gestire il WebSocket.

Ora ci creiamo un generico handler HTTP asincrono (disponibile con .NET 4.5) e ne deriviamo una classe specializzata. Registriamo il nuovo HTTP handler nel web.config dell'applicazione e ci creiamo una pagina javascript di test utilizzando le API Esri Javascript. Nello specifico possiamo utilizzare la classe StreamLayer che permette di visualizzare feature in real time da GEP ma anche da un WebSocket che fornisce feature in formato Esri JSON.


namespace StreamLayerDemo
{
    using System;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Net.WebSockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.WebSockets;
 
    [SuppressMessage("StyleCop.CSharp.DocumentationRules""SA1600:ElementsMustBeDocumented", Justification = "Demo code")]
    public abstract class WebSocketAsyncHandler : HttpTaskAsyncHandler
    {
        /// <summary>
        /// Gets a value indicating whether this handler can be reused for another request.
        /// Should return false in case your Managed Handler cannot be reused for another request, or true otherwise.
        /// Usually this would be false in case you have some state information preserved per request.
        /// You will need to configure this handler in the Web.config file of your 
        /// web and register it with IIS before being able to use it. For more information
        /// see the following link: <see cref="http://go.microsoft.com/?linkid=8101007" />
        /// </summary>
        public override bool IsReusable
        {
            get
            {
                return false;
            }
        }
 
        private WebSocket Socket { getset; }
 
        public override async Task ProcessRequestAsync(HttpContext httpContext)
        {
            await Task.Run(() =>
            {
                if (httpContext.IsWebSocketRequest)
                {
                    httpContext.AcceptWebSocketRequest(async delegate(AspNetWebSocketContext context)
                    {
                        this.Socket = context.WebSocket;
 
                        while (this.Socket != null || this.Socket.State != WebSocketState.Closed)
                        {
                            try
                            {
                                switch (this.Socket.State)
                                {
                                    case WebSocketState.Connecting:
                                        this.OnConnecting();
                                        break;
                                    case WebSocketState.Open:
                                        this.OnOpen();
                                        break;
                                    case WebSocketState.CloseSent:
                                        this.OnClosing(falsestring.Empty);
                                        break;
                                    case WebSocketState.CloseReceived:
                                        this.OnClosing(truestring.Empty);
                                        break;
                                }
                            
                            
                                ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024]);
                                WebSocketReceiveResult receiveResult = await this.Socket.ReceiveAsync(buffer, CancellationToken.None);
 
 
                                switch (receiveResult.MessageType)
                                {
                                    case WebSocketMessageType.Text:
                                        string message = Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
                                        this.OnMessageReceived(message);
                                        break;
                                    case WebSocketMessageType.Binary:
                                        this.OnMessageReceived(buffer.Array);
                                        break;
                                    case WebSocketMessageType.Close:
                                        this.OnClosing(true, receiveResult.CloseStatusDescription);
                                        break;
                                }
                            }
                            catch (Exception ex)
                            {
                                this.OnError(ex);
                            }
                        }
                    });
                }
            });
        }
 
        protected virtual void OnConnecting()
        {
        }
 
        protected virtual void OnOpen()
        {
        }
 
        protected virtual void OnMessageReceived(string message)
        {
        }
 
        protected virtual void OnMessageReceived(byte[] bytes)
        {
        }
 
        protected virtual void OnClosing(bool isClientRequest, string message)
        {
        }
 
        protected virtual void OnClosed()
        {
        }
 
        protected virtual void OnError(Exception ex)
        {
        }
 
        [DebuggerStepThrough]
        protected async Task SendMessageAsync(byte[] message)
        {
            await this.SendMessageAsync(message, WebSocketMessageType.Binary);
        }
 
        [DebuggerStepThrough]
        protected async Task SendMessageAsync(string message)
        {
            await this.SendMessageAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text);
        }
 
        private async Task SendMessageAsync(byte[] message, WebSocketMessageType messageType)
        {
            await this.Socket.SendAsync(
                new ArraySegment<byte>(message),
                messageType,
                true,
                CancellationToken.None);
        }
    }
}

Deriviamo dalla classe astratta HttpTaskAsyncHandler ed eseguiamo l'override della proprietà IsReusable e del metodo ProcessRequest ed utilizzeremo async/await e la classe task perchè processiamo task asincroni.

In questa classe ci limitiamo a  memorizzare l'istanza del WebSocket con una proprietà, verificare se la richiesta http è una richiesta WebSocket (IsWebSocketRequest) e in funzione dello stato del WebSocket richiamare il corrispondente metodo virtuale. Inoltre implementiamo il codice per inviare messaggi al client. Il WebSocket permette di inviare e ricevere messaggi (testo e binario) in modalità asincrona. Questa classe generica permette di implementare la propria classe specializza handler  WebSocket.

Le implementazioni dei metodi virtuali saranno nella classe derivata:

namespace StreamLayerDemo
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Threading.Tasks;
 
    [SuppressMessage("StyleCop.CSharp.DocumentationRules""SA1600:ElementsMustBeDocumented", Justification = "Demo code")]
    public class StreamLayerWebSocketAsyncHandler : WebSocketAsyncHandler
    {
        protected override void OnOpen()
        {
            PointTicker.DefaultInstance.Update += this.PointTicker_Update;
            base.OnOpen();
        }
 
        protected override void OnClosing(bool isClientRequest, string message)
        {
            PointTicker.DefaultInstance.Update -= this.PointTicker_Update;
            base.OnClosing(isClientRequest, message);
        }
 
        protected override void OnMessageReceived(string message)
        {
            // Assignment prevents warning "Because this call is not awaited...Consider applying the 'await' operator
            // This is intentional => fire and forget
            
            //Task task = this.SendMessageAsync("Your message is: " + message);
            
        }
 
        protected override void OnError(Exception ex)
        {
            // Assignment prevents warning "Because this call is not awaited...Consider applying the 'await' operator
            // This is intentional => fire and forget
            var task = this.SendMessageAsync(string.Format("Something exceptional happened: {0}", ex.Message));
        }
 
        private void PointTicker_Update(object sender, PointTickerEventArgs e)
        {
            // Assignment prevents warning "Because this call is not awaited...Consider applying the 'await' operator
            // This is intentional => fire and forget
            var task = this.SendMessageAsync(e.Feature);
 
        }
    }
}


Per simulare l'invio di dati (in questo esempio dei Point casuali in un certo extent) utilizziamo una singola istanza di una classe che implementa un semplice Timer che ogni 5 secondi invia una feature Point al client. I metodi Start e Stop della classe vengo richiamati negli eventi globali (global.asax.cs) Start e Stop dell'applicazione.

namespace StreamLayerDemo
{
    using System;
    using System.Diagnostics.CodeAnalysis;
 
    [SuppressMessage("StyleCop.CSharp.DocumentationRules""SA1600:ElementsMustBeDocumented", Justification = "Demo code")]
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            // var hostFactory = new PointTickerHostFactory();
            // var route = new ServiceRoute("PointTicker", hostFactory, typeof(PointTickerService));
            // System.Web.Routing.RouteTable.Routes.Add(route);
            PointTicker.DefaultInstance.Start();
        }
 
        protected void Application_End(object sender, EventArgs e)
        {
            PointTicker.DefaultInstance.Stop();
        }
    }
}


Classe per simulare l'invio di dati al client:

namespace StreamLayerDemo
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Timers;
 
    [SuppressMessage("StyleCop.CSharp.DocumentationRules""SA1600:ElementsMustBeDocumented", Justification = "Demo code")]
    public class PointTicker
    {
        private const int TimerInterval = 5000;
 
        private static object lockField = new object();
 
        private static PointTicker defaultInstanceField;
 
        private PointTicker()
        {
        }
 
        public event EventHandler<PointTickerEventArgs> Update;
 
        public static PointTicker DefaultInstance
        {
            get
            {
                lock (PointTicker.lockField)
                {
                    if (PointTicker.defaultInstanceField == null)
                    {
                        PointTicker.defaultInstanceField = new PointTicker();
                        PointTicker.defaultInstanceField.Initialize();
                    }
                }
 
                return PointTicker.defaultInstanceField;
            }
        }
 
        private static Timer Timer { getset; }
 
        public void Start()
        {
            lock (PointTicker.lockField)
            {
                if (!PointTicker.Timer.Enabled)
                {
                    PointTicker.Timer.Start();
                }
            }
        }
 
        public void Stop()
        {
            lock (PointTicker.lockField)
            {
                if (PointTicker.Timer.Enabled)
                {
                    PointTicker.Timer.Stop();
                }
            }
        }
 
        protected virtual void OnUpdate(string feature)
        {
            if (this.Update != null)
            {
                this.Update(
                    this,
                    new PointTickerEventArgs()
                    {
                        Feature = feature
                    });
            }
        }
 
        private void Initialize()
        {
            PointTicker.Timer = new Timer(PointTicker.TimerInterval);
            PointTicker.Timer.Elapsed += this.Timer_Elapsed;
        }
 
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            Random random = new Random();
            string feature = string.Format("{{\"geometry\" : {{\"x\" : {0}, \"y\" : {1} }}, \"attributes\" : {{\"ObjectId\" : {2}, \"RouteID\" : 1, \"DateTimeStamp\" : {3} }}}}", random.NextDouble(8.40, 8.95).ToString(new System.Globalization.CultureInfo("en-US")), random.NextDouble(45.23, 45.85).ToString(new System.Globalization.CultureInfo("en-US")), random.Next(1, Int32.MaxValue), DateTime.Now.UnixTicks().ToString(new System.Globalization.CultureInfo("en-US")));
 
            this.OnUpdate(feature);
        }
    }
}

Ora registriamo l'handler nel web.config per indicare ad IIS di utilizzare questo handler quando richiamato.

<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <handlers>
      <add name="StreamLayerWebSocketAsyncHandler" verb="*" path="wsStreamLayer" type="StreamLayerDemo.StreamLayerWebSocketAsyncHandler, StreamLayerDemo" resourceType="Unspecified" />
    </handlers>
  </system.webServer>
</configuration>

Nel Type indicheremo la classe comprensiva del namespace affinchè IIS possa trovarla e nel path indicheremo il nome del WebSocket che utilizzeremo per chiamarlo (in questo caso l'ho chiamato wsStreamLayer):
ws://<yourdomain>/<site:port>/wsStreamLayer

Per le creazione del collegamento, i WebSocket sfruttano il comando Http 'Upgrade' che indica al server che stiamo tentando di passare ad una connessione WebSocket




Come possiano notare da fiddler abbiamo anche Origin: questa origine è quella visionata dal server per capire da dove provengono i messaggi. Mentre Sec-WebSocket-Key è la chiave che compone la prima parte dell'handshake. E' generata in modo causale e codificata come stringa base64 di 16 byte. Sec-WebSocket-Version permette al server di rispondere con la versione del protocollo più adeguata alla versione supportata dal client.
In risposta dal server Sec-WebSocket-Accept ha la chiave non codificata inviata dal client concatenata con 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 e codificata in SHA-1 e successivamente in base64.
Inoltre come avviene per l'http è possibile effettuare la comunicazione WebSocket su ssl/tls tramite wss:
wss://<yourdomain>/<site:port>/wsStreamLayer

A questo punto testiamo con la classe StreamLayer delle API js Esri:

 

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
    <title>StreamLayer using ArcGIS API for JavaScript</title>
    <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/tundra/tundra.css">
    <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">
    <style type="text/css">
        htmlbody {
            height100%;
            width100%;
            margin0;
            padding0;
        }
 
        body {
            background-color#fff;
            overflowhidden;
            font-familysans-serif;
        }
 
        #map {
            width100%;
            height80%;
        }
    </style>
    <script src="http://js.arcgis.com/3.10/"></script>
</head>
<body class="tundra">
    <div id="map"></div>
    <div>
        <span>Enter websocket connection: </span><input type="text" id="txtWsUrl" value="ws://localhost:55555/wsStreamLayer" style="width400px" /><br />
        <input type="button" id="cmdNewStream" value="Make Stream Layer" />
        <input type="button" id="cmdDisconnect" value="Disconnect Stream Layer" />
    </div>
 
 
    <script>
        var curTime = new Date();
        var curTimeStamp = Date.parse(curTime.toUTCString());
        var layerDefinition = {
            "geometryType""esriGeometryPoint",
            "timeInfo": {
                "startTimeField""DateTimeStamp",
                "endTimeField"null,
                "trackIdField""RouteID",
                "timeReference"null,
                "timeInterval": 1,
                "timeIntervalUnits""esriTimeUnitsMinutes",
                "exportOptions": {
                    "useTime"true,
                    "timeDataCumulative"false,
                    "timeOffset"null,
                    "timeOffsetUnits"null
                },
                "hasLiveData"true
            },
            "fields": [
              {
                  name: "ObjectId",
                  type: "esriFieldTypeOID",
                  alias: "ObjectId"
              },
              {
                  name: "DateTimeStamp",
                  type: "esriFieldTypeDate",
                  alias: "DateTimeStamp"
              },
              {
                  name: "RouteID",
                  type: "esriFieldTypeInteger",
                  alias: "RouteID"
              }
            ]
        };
 
        var map, featureCollection, streamLayer;
 
        require(["esri/map",
          "esri/TimeExtent",
          "esri/layers/StreamLayer",
          "esri/InfoTemplate",
          "esri/symbols/SimpleMarkerSymbol",
          "esri/symbols/SimpleLineSymbol",
          "esri/renderers/SimpleRenderer",
          "esri/renderers/TimeClassBreaksAger",
          "esri/renderers/TemporalRenderer",
          "esri/Color",
          "dojo/dom",
          "dojo/on",
          "dojo/domReady!"
        ], function (Map, TimeExtent, StreamLayer, InfoTemplate, SimpleMarkerSymbol, SimpleLineSymbol, SimpleRenderer, TimeClassBreaksAger, TemporalRenderer, Color, dom, on) {
            var trackedBusses = {}, cnt = 0;
 
            map = new Map("map", {
                basemap: "gray",
                center: [8.675, 45.54],
                zoom: 10
            });
 
            // event listeners for button clicks
            on(dom.byId("cmdNewStream"), "click", makeNewStreamLayer);
            on(dom.byId("cmdDisconnect"), "click", disconnectStreamLayer);
 
            function makeStreamLayer() {
                //Make FeatureCollection to define layer without using url
                featureCollection = {
                    "layerDefinition"null,
                    "featureSet": {
                        "features": [],
                        "geometryType""esriGeometryPoint"
                    }
                };
                featureCollection.layerDefinition = layerDefinition;
 
                // Instantiate StreamLayer
                // 1. socketUrl is the url to the GeoEvent Processor web socket.
                // 2. purgeOptions.displayCount is the maximum number of features the
                //    layer will display at one time
                // 3. trackIdField is the name of the field that groups features
                var layer = new StreamLayer(featureCollection, {
                    socketUrl: txtWsUrl.value,
                    purgeOptions: { displayCount: 500 },
                    trackIdField: featureCollection.layerDefinition.timeInfo.trackIdField,
                    infoTemplate: new InfoTemplate("Route Id: ${RouteID}""Timestamp: ${DateTimeStamp}")
                });
                console.log("TrackID: ", featureCollection.layerDefinition.timeInfo.trackIdField);
                console.log("TrackID: ", layer.timeInfo.trackIdField);
 
                //Make renderer and apply it to StreamLayer
                var renderer = makeRenderer();
                layer.setRenderer(renderer);
 
                //Subscribe to onMessage event of StreamLayer so can adjust map time
                layer.on("message", processMessage);
                layer.on("connect", connectevt);
                layer.on("error", errorevt);
                return layer;
            }
 
            function connectevt() {
                console.log("Connesso");
            }
 
            function errorevt() {
                console.log("error");
            }
 
            // Process message that StreamLayer received.
            function processMessage(message) {
                if (featureCollection.layerDefinition.timeInfo &&
                    featureCollection.layerDefinition.timeInfo.startTimeField) {
                    var timestamp = message.attributes[featureCollection.layerDefinition.timeInfo.startTimeField];
                    if (!map.timeExtent) {
                        map.setTimeExtent(new esri.TimeExtent(new Date(timestamp), new Date(timestamp)));
                        console.log("TIME EXTENT: ", map.timeExtent);
                    } else {
                        var tsEnd = Date.parse(map.timeExtent.endTime.toString());
                        if (timestamp > tsEnd) {
                            map.setTimeExtent(new esri.TimeExtent(map.timeExtent.startTime, new Date(timestamp)));
                            console.log("TIME EXTENT: ", map.timeExtent);
                        }
                    }
                }
            }
 
            // Make new StreamLayer and add it to map.
            function makeNewStreamLayer() {
                disconnectStreamLayer();
                streamLayer = makeStreamLayer();
                map.addLayer(streamLayer);
            }
 
            // Disconnect StreamLayer from websocket and remove it from the map
            function disconnectStreamLayer() {
                if (streamLayer) {
                    streamLayer.suspend();
                    streamLayer.disconnect();
                    streamLayer.clear();
                    map.removeLayer(streamLayer);
                    streamLayer = null;
                    //map.timeExtent = null;
                }
            }
 
            // Make temporal renderer with latest observation renderer
            function makeRenderer() {
                var obsRenderer = new SimpleRenderer(
                  new SimpleMarkerSymbol("circle", 8,
                  new SimpleLineSymbol("solid",
                  new Color([5, 112, 176, 0]), 1),
                  new Color([5, 112, 176, 0.4])
                ));
 
                var latestObsRenderer = new SimpleRenderer(
                  new SimpleMarkerSymbol("circle", 12,
                  new SimpleLineSymbol("solid",
                  new Color([5, 112, 176, 0]), 1),
                  new Color([5, 112, 176])
                ));
 
                var temporalRenderer = new TemporalRenderer(obsRenderer, latestObsRenderer, nullnull);
                return temporalRenderer;
            }
        });
    </script>
</body>
</html> 
 
 
 
 
Scaricare qui la soluzione.

sabato 31 maggio 2014

TypeScript

Oramai l'importanza di JavaScript nello sviluppo di applicazioni è ben noto a tutti. Il problema principale però rimane quando si sviluppano applicazioni di una certa complessità.
La risposta di Microsoft è stata TypeScript che ha anche utilizzato il linguaggio in casa propria per progetti come Monaco, la versione web-based di Visual Studio.
Il compilatore di TypeScript è implementato in linguaggio TypeScript ed è su CodePlex.
TypeScript è un superset di JavaScript, per la precisione un superset della sintassi ECMAScript 5 (ES5), cioè un linguaggio che estende JavaScript ma che produce in output codice JavasScript a seguito della compilazione. Essendo un superset, ogni programma JavaScript è anche un programma TypeScript. Inoltre la sintassi TypeScript include diverse caratteristiche proposte dell'ECMAScript 6 (ES6) che comprendono classi e moduli. Estendendo Javascript con tipi, classi, interfacce e moduli, si agevola lo sviluppo di applicazioni facilmente scalabili e si permette una maggiore produttività con i tool di sviluppo (refactoring, debugging, intellisense, tipi verificati staticamente ecc.). In sostanza tramite la type-safety ed una programmazione object-oriented si tutela lo sviluppo senza perdere i vantaggi di javascript, cross-platform in primis.
TypeScript 1.0 è disponibile con l'update 2 di Visual Studio 2013 , come plug-in Visual Studio 2012 o anche come package di Node.js ( npm install -g typescript) ma è disponibile anche in altri IDE/Editor (Eclipse, JetBrains IDE, Sublime Text, Emacs, Vim ecc.).
Sul sito typescriptlang.org potete trovare tutte le risorse (tutorial, help, playground ecc.) riguardo a TypeScript.
In Javascript si utilizzano molte librerie di terze parti (dojo, jquery ecc.) e per poterle utilizzare con TypeScript occorre innanzitutto dichiarare la libreria che si vuole utilizzare tramite il classico tag script. Per quel che riguarda lo sviluppo essa ha esclusivamente l'obiettivo di consentirci il normale funzionamento ma, dal punto di vista delle definizioni, non ha alcun effetto pratico.
Per permettere al compilatore TypeScript di conoscere le definizioni, occorre innanzitutto procurarsi il file di definizione "libreria.d.ts" reperibile ad esempio su github. Esiste una collezione di definizioni molto estesa e aggiornata che è possibile trovare a questo indirizzo: https://github.com/borisyankov/DefinitelyTyped o tramite nuget da console Get-Package -Filter DefinitelyTyped -ListAvailable. Il file di definizione permette di avere a disposizione l'intellisense ma soprattutto di conoscere i tipi validando il codice.
Il file di definizione per ArcGIS API for JavaScript lo trovate nel repository Esri su github all'indirizzo: https://github.com/Esri/jsapi-resources/tree/master/typescript

Infine vediamo un semplicissimo esempio con ArcGIS API for JavaScript.

Creiamo un nuovo progetto in Visual Studio 2013 utilizzando il template TypeScript:


 Importiamo il file di definizione esri.d.ts nella soluzione:


Ora ci creiamo nel progetto un nuovo file TypeScript Point.ts (i file hanno estensione ts) e, ad esempio, estendiamo la classe Point di Esri. Le classi TypeScript supportano anche l'ereditarietà.
Referenziamo il file di definizione esri.d.ts mediante una sintassi basata su un commento:

/// <reference path="../esri.d.ts" />

Ora aggiungiamo un metodo alla classe Point. Importiamo il modulo esterno di interesse assegnandoci un alias (AGSPoint)  ed estendiamo la nostra classe (Point) con la parola chiave extends aggiungendo il metodo log() ed infine indichiamo che la classe potrà essere visibile all'esterno con export (può anche essere utilizzato anche per proprietà, metodi, tipi statici e anche semplici variabili)



e questo è il relativo js prodotto (Point.js):



Ora aggiungiamo un altro file TypeScript (app.ts) dove utilizziamo, a titolo di esempio, la classe Point del modulo esterno Point.ts nel metodo di ingresso della pagine html per testarne il funzionamento:

Importiamo il modulo esterno (Point.ts) ed utilizziamo la classe Point (in questo caso ho impostato l'alias p).
Come possiamo notare quando scriviamo il codice ci viene in aiuto l'intellisense:


e questa è la nostra pagina html che richiama la funzione in app.ts



 Eseguiamo e verifichiamo dalla console del browser il risultato:


 e possiamo, come detto, andare in debug: