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

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

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



lunedì 25 febbraio 2013

Alziamo il livello: 3D surface!

In ArcGIS è disponibile la toolbar 3D Analyst, sia in ArcMap che in ArcScene. Una volta abilitata l'estensione 3D Analyst, abbiamo a disposizione strumenti che ci consentono di operare su superfici 3D. Gli strumenti lavorano con TIN, raster, terrain dataset o LAS dataset e forniscono feedback in tempo reale sui punti digitalizzati sulla superficie. Comunque per dataset terrain e LAS sono presenti alcune restrizioni nell'utilizzo dei tool interattivi.
Gli strumenti interattivi sono:
  • Create Countours
  • Create Steepest Path
  • Create Line of Sight
  • Interpolate Point
  • Interpolate Line
  • Interpolate Polygon
  • Create Profiles

Nel menu a discesa della toolbar 3D Analyst sono disponibili delle opzioni di visualizzazione e interpolazione per la creazione dei profili. Per maggiori dettagli sui tool interattivi 3D Analyst seguire il seguente link.

Se volessimo utilizzare le stesse funzionalità come servizio possiamo servirci di metodi che gestiscono le surface che, nel caso specifico, sono esposti dell' interfaccia ISurface. L'interfaccia è nella libreria Geodatabase degli ArcObjects.

Ad esempio, se dovessimo creare una LOS (Line of Sight) utilizziamo il metodo GetLineOfSight. Va sottolineato che per questo metodo in .NET è consigliato comunque usare IGeoDatabaseBridge2.GetLineOfSight che fornisce la stessa funzionalità ma con interoperabilità sicura. Ricordarsi anche che la classe che implementa IGeoDatabaseBrigde2, GeodatabaseHelper è singleton e pertanto occorre utilizzare l'activator. Se analizziamo nel dettaglio il metodo, osserviamo che è richiesta la ISurface, il punto dell'osservatore, il punto osservato (target), se applicare la curvatura terrestre (utilizzabile solo se la surface ha un sistema di coordinate proiettate con definita l'unità di misura della Z), se considerare la rifrazione atmosferica (utilizzabile solo se la surface ha un sistema di coordinate proiettate con definita l'unità di misura della Z) e il fattore di rifrazione (se il valore non viene fornito il valore predefinito è 0.13).
La formula utilizzata per la correzione della rifrazione atmosferica è:

Z = Z0 + D2(R - 1) ÷ d

Z = Valore di quota corretto dopo aver compensato l'influenza della rifrazione atmosferica
Z0 = Quota dell'osservatore
D = distanza planimetrica tra l'osservatore e l'osservato
d = diametro della terra (12740 Km)
R = coefficiente di rifrazione della luce (default 0.13)
 

Il metodo restituisce le linee visibili e le linee invisibili mediante singole polyline dall'osservatore al target e se il target è visibile. Le linee hanno i valori Z interpolati sulla superficie. Chiaramente è possibile avere anche una delle due polilinee nulle e quindi visibilità completa o meno.
Il metodo restituisce anche il punto di obstruction ovvero se il target non è visibile, il primo punto di intersezione dall'osservatore verso il target tra la superficie e la LOS che è utilizzata. Anche qui, se il target è visibile dall'osservatore, il punto di obstruction sarà nullo.



        /// <summary>
        /// Method for implementing REST operation "GetLineOfSight"'s functionality.
        /// </summary>
        /// <param name="boundVariables">object boundVariables</param>
        /// <param name="operationInput">object operationInput</param>
        /// <param name="outputFormat">object outputFormat</param>
        /// <param name="requestProperties">object requestProperties</param>
        /// <param name="responseProperties">object responseProperties</param>
        /// <returns>String JSON representation of output</returns>
        private byte[] GetLineOfSightOperHandler(NameValueCollection boundVariables, JsonObject operationInput, string outputFormat, string requestProperties, out string responseProperties)
        {
            responseProperties = "{\"Content-Type\" : \"application/json\"}";
            int layerID = System.Convert.ToInt32(boundVariables["SurfaceLayersID"], CultureInfo.InvariantCulture);
 
            AnalysisSurface analysisSurface = this.GetSurfaceLayerInfo(layerID).GetAnalysisSurface();
 
            // offsetObserver
            double? offsetObserver;
            operationInput.TryGetAsDouble("offsetObserver"out offsetObserver);
 
            // offsetTarget
            double? offsetTarget;
            operationInput.TryGetAsDouble("offsetTarget"out offsetTarget);
 
            JsonObject jsonLine;
            if (!operationInput.TryGetJsonObject("geometry"out jsonLine))
            {
                throw new SurfaceUtilityException("Geometry is wrong!");
            }
 
            IPolyline polyline = jsonLine.ConvertAnyJsonGeom() as IPolyline;
            if (polyline == null)
            {
                throw new SurfaceUtilityException("Geometry is wrong!");
            }
 
            bool? applyCurvature;
            operationInput.TryGetAsBoolean("applyCurvature"out applyCurvature);
 
            if (!applyCurvature.HasValue)
            {
                applyCurvature = false;
            }
 
            bool? applyRefraction;
            operationInput.TryGetAsBoolean("applyRefraction"out applyRefraction);
 
            if (!applyRefraction.HasValue)
            {
                applyRefraction = false;
            }
 
            double? refractionFactor;
            operationInput.TryGetAsDouble("refractionFactor"out refractionFactor);
            object refractionFactorValue = Type.Missing;
 
            if (refractionFactor.HasValue)
            {
                refractionFactorValue = refractionFactor.Value;
            }
 
            IPoint pointFrom = polyline.FromPoint;
            IPoint pointTo = polyline.ToPoint;
            ISurface surface = analysisSurface.Surface;
            pointFrom.Z = surface.GetElevation(pointFrom);
            pointTo.Z = surface.GetElevation(pointTo);
 
            if (surface.IsVoidZ(pointFrom.Z) || surface.IsVoidZ(pointTo.Z))
            {
                throw new SurfaceUtilityException("End points line not valid!");
            }
 
            if (offsetObserver.HasValue)
            {
                pointFrom.Z += offsetObserver.Value;
            }
 
            if (offsetTarget.HasValue)
            {
                pointTo.Z += offsetTarget.Value;
            }
 
            IPoint pointObstruction;
            IPolyline visibleLines;
            IPolyline invisibleLines;
            bool isVisible;
 
            // applyCurvature and applyRefraction can be true if the surface has a defined projected coordinate system that includes defined ZUnits. 
            try
            {
                if (!surface.CanDoCurvature)
                {
                    applyCurvature = false;
                    applyRefraction = false;
                }
            }
            catch
            {
                applyCurvature = false;
                applyRefraction = false;
            }
 
            Type t = Type.GetTypeFromProgID("esriGeodatabase.GeoDatabaseHelper");
            System.Object obj = Activator.CreateInstance(t);
            IGeoDatabaseBridge2 geoDatabaseBridge = obj as IGeoDatabaseBridge2;
            geoDatabaseBridge.GetLineOfSight(surface, pointFrom, pointTo, out pointObstruction, out visibleLines, out invisibleLines, out isVisible, applyCurvature.Value, applyRefraction.Value, ref refractionFactorValue);
 
            JsonObject result = new JsonObject();
            result.AddJsonObject("pointObstruction"Conversion.ToJsonObject(pointObstruction));
            result.AddJsonObject("visibleLines"Conversion.ToJsonObject(visibleLines));
            result.AddJsonObject("invisibleLines"Conversion.ToJsonObject(invisibleLines));
            result.AddBoolean("isVisible", isVisible);
            return result.JsonByte();
        }

Un altro interessante metodo è il Locate che permette di individuare l'intersezione tra la surface e il ray. Un ray ha un end point che è la sua origine e continua indefinitamente nell'altra direzione: la direzione viene fornita mediante un vettore. Come possiamo notare dall'immagine (2° caso) se il punto di intersezione con la surface è contenuto tra i due punti che derminano l'origine e la direzione allora l'intersezione coincide con il punto di obstruction della LOS.



        /// <summary>
        /// Method for implementing REST operation "GetLocate"'s functionality.
        /// </summary>
        /// <param name="boundVariables">object boundVariables</param>
        /// <param name="operationInput">object operationInput</param>
        /// <param name="outputFormat">object outputFormat</param>
        /// <param name="requestProperties">object requestProperties</param>
        /// <param name="responseProperties">object responseProperties</param>
        /// <returns>String JSON representation of output</returns>
        private byte[] GetLocateHandler(NameValueCollection boundVariables, JsonObject operationInput, string outputFormat, string requestProperties, out string responseProperties)
        {
            responseProperties = "{\"Content-Type\" : \"application/json\"}";
            int layerID = System.Convert.ToInt32(boundVariables["SurfaceLayersID"], CultureInfo.InvariantCulture);
 
            AnalysisSurface analysisSurface = this.GetSurfaceLayerInfo(layerID).GetAnalysisSurface();
 
            JsonObject jsonGeometry;
            if (!operationInput.TryGetJsonObject("geometry"out jsonGeometry))
            {
                throw new SurfaceUtilityException("Geometry is wrong!");
            }
 
            IPolyline polyline = jsonGeometry.ConvertAnyJsonGeom() as IPolyline;
            if (polyline == null)
            {
                throw new SurfaceUtilityException("Geometry is wrong!");
            }
 
            IPoint pointFrom = polyline.FromPoint;
            IPoint pointTo = polyline.ToPoint;
            ISurface surface = analysisSurface.Surface;
 
            // offsetFromPoint
            double? offsetFromPoint;
            if (operationInput.TryGetAsDouble("offsetFromPoint"out offsetFromPoint) && offsetFromPoint.HasValue && offsetFromPoint != double.NaN)
            {
                pointFrom.Z = surface.GetElevation(pointFrom);
                pointFrom.Z += offsetFromPoint.Value;
            }
 
            if (surface.IsVoidZ(pointFrom.Z))
            {
                throw new SurfaceUtilityException("Start point line not valid!");
            }
 
            // offsetToPoint
            double? offsetToPoint;
            if (operationInput.TryGetAsDouble("offsetToPoint"out offsetToPoint) && offsetToPoint.HasValue && offsetToPoint != double.NaN)
            {
                pointTo.Z = surface.GetElevation(pointTo);
                pointTo.Z += offsetToPoint.Value;
            }
 
            if (surface.IsVoidZ(pointTo.Z))
            {
                throw new SurfaceUtilityException("End point line not valid!");
            }
 
            // hint
            long? hint;
            operationInput.TryGetAsLong("hint"out hint);
            int hintValue = 0;
            if (operationInput.TryGetAsLong("hint"out hint) && hint.HasValue)
            {
                hintValue = (int)hint.Value;
            }
 
            IRay ray = new RayClass();
            ray.Origin = pointFrom;
            ray.Vector = GeometryUtility.ConstructVector3D(pointTo.X - pointFrom.X, pointTo.Y - pointFrom.Y, pointTo.Z - pointFrom.Z);
 
            
            IPoint point = surface.Locate(ray, hintValue);
 
            JsonObject result = new JsonObject();
            result.AddJsonObject("geometry"Conversion.ToJsonObject(point, true));
            
            return result.JsonByte();
        }



In ArcGIS Runtime for WPF possiamo richiamare la nostra SOE ed invocare le operazioni in modalità asincrona utilizzando await e async:

/// <summary>
    /// Class Surface SOE
    /// </summary>
    internal class SurfaceSOE
    {
        /// <summary>
        /// soe name
        /// </summary>
        private string soeName;
 
        /// <summary>
        /// url map service soe
        /// </summary>
        private Uri mapServiceUri;
 
        /// <summary>
        /// resource index soe
        /// </summary>
        private int resourceIndex;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="SurfaceSOE"/> class
        /// </summary>
        /// <param name="mapServiceUrl">url of map service</param>
        /// <param name="soeName">soe name</param>
        /// <param name="resourceIndex">index of resource</param>
        public SurfaceSOE(string mapServiceUrl, string soeName, int resourceIndex)
        {
            this.soeName = soeName;
            this.mapServiceUri = new Uri(mapServiceUrl);
            this.resourceIndex = resourceIndex;                        
        }
 
        /// <summary>
        /// Prevents a default instance of the <see cref="SurfaceSOE"/> class from being created.
        /// </summary>
        private SurfaceSOE()
        {
        }
 
        /// <summary>
        /// delegate Operation Result Handler
        /// </summary>
        /// <param name="jsonResponse">response json</param>
        /// <param name="source">object source</param>
        private delegate void OperationResultHandler(string jsonResponse, object source);
 
        /// <summary>
        /// delegate Get Line Of Sight Response Event
        /// </summary>
        public event EventHandler GetLineOfSightResponseEvent;
 
        /// <summary>
        /// enumerator of Operation in soe
        /// </summary>
        public enum OperationName
        {
            /// <summary>
            /// operation Line of Sight
            /// </summary>
            GetLineOfSight
        }
 
        /// <summary>
        /// validate if soe is online
        /// </summary>
        /// <returns>true is online</returns>
        public async Task<bool> Validate()
        {
            try
            {
                Uri requestUri = new Uri(this.mapServiceUri, string.Format("exts/{0}/surfaceLayers/{1}?f=json"this.soeName, this.resourceIndex));
                string jsonSOEInfo = await new WebClient().DownloadStringTaskAsync(requestUri);
 
                JavaScriptSerializer serializer = new JavaScriptSerializer();
                serializer.RegisterConverters(new JavaScriptConverter[] { new DynamicJsonConverter() });
 
                dynamic infoSOE = serializer.Deserialize(jsonSOEInfo, typeof(object)) as dynamic;
 
                var x = infoSOE.id;
 
                return true;
            }
            catch
            {
                return false;
            }
        }
 
        /// <summary>
        /// operation Line of Sight
        /// </summary>
        /// <param name="line">geometry line</param>
        /// <param name="offsetObserver">offset Observer</param>
        /// <param name="offsetTarget">offset Target</param>
        public async void GetLineOfSight(Polyline line, double offsetObserver, double offsetTarget)
        {
            string operationParams = string.Format(System.Globalization.CultureInfo.InvariantCulture, "geometry={0}&offsetObserver={1}&offsetTarget={2}&f=json", line.ToJson(), offsetObserver, offsetTarget);
            string jsonResponse = await this.InvokeOperation("GetLineOfSight", operationParams);
 
            GetLineOfSightResponse response = new GetLineOfSightResponse(jsonResponse);
 
            if (this.GetLineOfSightResponseEvent != null)
            {
                this.GetLineOfSightResponseEvent(this, response);
            }
        }
 
        /// <summary>
        /// invoke of operation
        /// </summary>
        /// <param name="operationName">name of operation</param>
        /// <param name="operationParameters">parameters of operation</param>
        /// <returns>response of operation</returns>
        private async Task<string> InvokeOperation(string operationName, string operationParameters)
        {
            Uri requestUri = new Uri(this.mapServiceUri, string.Format("exts/{0}/surfaceLayers/{1}/{2}?{3}"this.soeName, this.resourceIndex, operationName, operationParameters));
            return await new WebClient().DownloadStringTaskAsync(requestUri);            
        }


MainWindows.xaml.cs
/// <summary>
    /// class MainWindow
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// url surface soe mapserver
        /// </summary>
        private static string surfaceSoeUrl = "http://sit.sistemigis.it/sit/rest/services/Demo/Surface/MapServer/";
        
        /// <summary>
        /// name soe
        /// </summary>
        private static string surfaceSoeName = "SurfaceUtility";
        
        /// <summary>
        /// index of surface resource
        /// </summary>
        private static int surfaceResourceIndex = 0;
 
        /// <summary>
        /// surface soe
        /// </summary>
        private SurfaceSOE surfaceSOE;
        
        /// <summary>
        /// current operation
        /// </summary>
        private SurfaceSOE.OperationName currentOperation;
        
        /// <summary>
        /// draw object
        /// </summary>
        private Draw drawObject;
        
        /// <summary>
        /// current symbol
        /// </summary>
        private Symbol currentSymbol;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class
        /// </summary>
        public MainWindow()
        {
            // License setting and ArcGIS Runtime initialization is done in Application.xaml.cs.
            this.InitializeComponent();
            this.InitializeSOE();
 
            this.drawObject = new Draw(_map)
            {
                LineSymbol = LayoutRoot.Resources["BlueLineSymbol"as LineSymbol
            };
 
            this.drawObject.DrawBegin += this.MyDrawObject_OnDrawBegin;
            this.drawObject.DrawComplete += this.MyDrawObject_OnDrawComplete;
        }
 
        /// <summary>
        /// event OnDrawBegin
        /// </summary>
        /// <param name="sender">object sender</param>
        /// <param name="args">object args</param>
        private void MyDrawObject_OnDrawBegin(object sender, EventArgs args)
        {
            GraphicsLayer graphicsLayer = _map.Layers["inputsGraphicsLayer"as GraphicsLayer;
            graphicsLayer.ClearGraphics();
        }
 
        /// <summary>
        /// event OnDrawComplete
        /// </summary>
        /// <param name="sender">object sender</param>
        /// <param name="args">object args</param>
        private void MyDrawObject_OnDrawComplete(object sender, DrawEventArgs args)
        {
            Polyline pl = (Polyline)args.Geometry;
            if (pl.Extent.Width == 0 && pl.Extent.Height == 0)
            {
                return;
            }
 
            this.Cursor = Cursors.Wait;
 
            this.drawObject.IsEnabled = false;
 
            switch (this.currentOperation)
            {
                case SurfaceSOE.OperationName.GetLineOfSight:
 
                    this.surfaceSOE.GetLineOfSight(pl, 10, 10);
                    break;
            }
        }
 
        /// <summary>
        /// initialize soe
        /// </summary>
        private async void InitializeSOE()
        {
            this.Cursor = Cursors.AppStarting;
 
            this.surfaceSOE = new SurfaceSOE(surfaceSoeUrl, surfaceSoeName, surfaceResourceIndex);
            bool isValid = await this.surfaceSOE.Validate();
            if (isValid)
            {
                this.surfaceSOE.GetLineOfSightResponseEvent += new EventHandler(this.SurfaceSOE_GetLineOfSightEvent);
            }
            else
            {
                MessageBox.Show("We are unable to continue due to invalid SOE properties.\nPlease contact your system administrator.""Can't continue"MessageBoxButton.OK, MessageBoxImage.Stop);
            }
 
            this.Cursor = Cursors.Arrow;
        }
 
        /// <summary>
        /// event click clear
        /// </summary>
        /// <param name="sender">object sender</param>
        /// <param name="e">object e</param>
        private void Clear_Click(object sender, RoutedEventArgs e)
        {
            GraphicsLayer graphicsLayer = _map.Layers["inputsGraphicsLayer"as GraphicsLayer;
            graphicsLayer.ClearGraphics();
            this.Cursor = Cursors.Arrow;
        }
 
        /// <summary>
        /// event click LOS
        /// </summary>
        /// <param name="sender">object sender</param>
        /// <param name="e">object e</param>
        private void LOS_Click(object sender, RoutedEventArgs e)
        {
            this.currentOperation = SurfaceSOE.OperationName.GetLineOfSight;
            this.currentSymbol = LayoutRoot.Resources["BlueLineSymbol"as Symbol;
            this.drawObject.DrawMode = DrawMode.LineSegment;
            this.drawObject.IsEnabled = true;
        }
 
        /// <summary>
        /// GetLineOfSight Event
        /// </summary>
        /// <param name="sender">object sender</param>
        /// <param name="e">object e</param>
        private void SurfaceSOE_GetLineOfSightEvent(object sender, EventArgs e)
        {
            GetLineOfSightResponse response = (GetLineOfSightResponse)e;
 
            if (response.VisibleLines != null)
            {
                Graphic graphic = new Graphic()
                {
                    Symbol = LayoutRoot.Resources["GreenLineSymbol"as Symbol,
                    Geometry = response.VisibleLines
                };
                GraphicsLayer graphicsLayer = _map.Layers["inputsGraphicsLayer"as GraphicsLayer;
                graphicsLayer.Graphics.Add(graphic);
            }
 
            if (response.InvisibleLines != null)
            {
                Graphic graphic = new Graphic()
                {
                    Symbol = LayoutRoot.Resources["RedLineSymbol"as Symbol,
                    Geometry = response.InvisibleLines
                };
                GraphicsLayer graphicsLayer = _map.Layers["inputsGraphicsLayer"as GraphicsLayer;
                graphicsLayer.Graphics.Add(graphic);
            }
 
            if (!response.IsVisible)
            {
                Graphic graphic = new Graphic()
                {
                    Symbol = LayoutRoot.Resources["RedCircleSymbol"as Symbol,
                    Geometry = response.PointObstruction
                };
                GraphicsLayer graphicsLayer = _map.Layers["inputsGraphicsLayer"as GraphicsLayer;
                graphicsLayer.Graphics.Add(graphic);
            }
 
            this.Cursor = Cursors.Arrow;
        }
    }

Qui potete scaricare l'esempio in WPF.

L'analisi di visibilità risulta particolarmente adatta per le valutazioni di impatto ambientale per aree sensibili all'impatto visivo (impianti eolici, viadotti ecc.). E' possibile creare anche mappe di visibilità con la funzione Viewshed utile anche ad esempio per sapere qual è una buona posizione per una torre di comunicazione.

Qui in questo esempio sono presenti quasi tutti i metodi dell'interfaccia ISurface esposti come SOE rest e quindi utilizzabili con tutti i web client ESRI. L'estensione denominata SurfaceUtility sarà disponibile per il download dopo la Esri International Developer Summit che si terrà dal 25 al 28 marzo 2013 a Palm Springs poichè i metodi dei profili sono ereditati da una SOE che ha sviluppato Esri e che verrà presentata ufficialmente in quell'occasione.
Ora l'estensione SurfaceUtility è disponibile qui.

Qui potete vedere un sample che utilizza la SOE e il generate di ArcGIS for Portal per visualizzare un profilo altimetrico da un file GPX.