jueves, 28 de febrero de 2019

Validador de DPI / CUI en C#

Este método valida un número de CUI / DPI de Guatemala.

Se basa en la implementación Java del siguiente blog http://xcodegt.blogspot.com/2016/09/funcion-para-validar-cui-guatemala-con.html

Se actualizó el dato del número de municipios por departamento, ya que ha variado desde 2016 (fecha del post original) a la fecha.

1:  private static bool ValidarDPI(string dpi)  
2:  {  
3:     var regex = "^[0-9]{4}-[0-9]{5}-[0-9]{4}$";  
4:     var test = Regex.IsMatch(dpi, regex);  
5:    
6:     if (!test)  
7:     {  
8:        return false;  
9:     }  
10:    
11:     var cui = dpi.Replace("-", "");  
12:    
13:     var numero = cui.Substring(0, 8);  
14:
15:
16:     var depto = Convert.ToInt32(cui.Substring(9, 2));  
17:     var muni = Convert.ToInt32(cui.Substring(11, 2));  
18:    
19:     var validador = Convert.ToInt32(cui.Substring(8, 1));  
20:    
21:     // Conteo de municipios por departamento  
22:     int[] munisPorDepto =   
23:     {  
24:        /* 01 - Guatemala    */ 17,  
25:        /* 02 - El Progreso   */ 8,  
26:        /* 03 - Sacatepéquez  */ 16,  
27:        /* 04 - Chimaltenango  */ 16,  
28:        /* 05 - Escuintla    */ 14,  
29:        /* 06 - Santa Rosa   */ 14,  
30:        /* 07 - Sololá     */ 19,  
31:        /* 08 - Totonicapán   */ 8,  
32:        /* 09 - Quetzaltenango */ 24,  
33:        /* 10 - Suchitepéquez  */ 21,  
34:        /* 11 - Retalhuleu   */ 9,  
35:        /* 12 - San Marcos   */ 30,  
36:        /* 13 - Huehuetenango  */ 33,  
37:        /* 14 - Quiché     */ 21,  
38:        /* 15 - Baja Verapaz  */ 8,  
39:        /* 16 - Alta Verapaz  */ 17,  
40:        /* 17 - Petén      */ 14,  
41:        /* 18 - Izabal     */ 5,  
42:        /* 19 - Zacapa     */ 11,  
43:        /* 20 - Chiquimula   */ 11,  
44:        /* 21 - Jalapa     */ 7,  
45:        /* 22 - Jutiapa     */ 17  
46:     };  
47:    
48:     if (muni == 0 || depto == 0)  
49:     {  
50:        return false;  
51:     }  
52:    
53:     if (depto > munisPorDepto.Length)  
54:     {  
55:        return false;  
56:     }  
57:    
58:     if (muni > munisPorDepto[depto - 1])  
59:     {  
60:        return false;  
61:     }  
62:    
63:     int total = 0;  
64:     for (int i = 0; i < numero.Length; i++)  
65:     {  
66:        total += (Convert.ToInt32(numero.Substring(i, 1))) * (i + 2);  
67:     }  
68:    
69:     int modulo = total % 11;  
70:    
71:     return modulo == validador;  
72:  }  

jueves, 19 de julio de 2018

Ejecutar consultas a un Feature Layer de Arcgis Online desde C#


En la documentación de ArcGIS Runtime SDK for .NET (versión de SDK 100.3.0), se encuentran ejemplos sobre autenticación y sobre consultas a un Feature Layer público, sin embargo, no encontré un ejemplo sobre una consulta a un Feature Layer privado (requiere autenticación), lo que me tomó algunas horas realizar.

Comparto el ejemplo realizado (app de consola):

Program.cs
  1. using System;
  2. using Esri.ArcGISRuntime.Data;
  3. using System.Collections.Generic;
  4. namespace arcgisAuthTest
  5. {
  6.     class Program
  7.     {
  8.        
  9.         static void Main(string[] args)
  10.         {
  11.             string serviceUrl = "https://services6.arcgis.com/FFF6MrlVn5PAw2F8/arcgis/rest/services/service_bf963161c24047259028034xxxxxx/FeatureServer/0";
  12.             // Would be better to read them from a secure source
  13.             string username = "usr";
  14.             string password = "pa$$";
  15.             FeatureLayerQuery flq = new FeatureLayerQuery(serviceUrl, username, password);
  16.             var i = 0;
  17.             string query = "upper(placa_motocicleta) LIKE '%342FFF%'";
  18.             List<string> outFields = new List<string>() {
  19.                 "*"
  20.             };
  21.             var listado = flq.QueryFeature(query, outFields);
  22.             foreach (Feature f in listado)
  23.             {
  24.                 Console.WriteLine(string.Format(Environment.NewLine "Feature {0}", i++));
  25.                 for (int j = 0; j < f.FeatureTable.Fields.Count; j++)
  26.                 {
  27.                     string fieldName = f.FeatureTable.Fields[j].Name;
  28.                     Console.WriteLine(string.Format("f[{0}] = '{1}'", fieldName, f.Attributes[fieldName]));
  29.                 }
  30.             }
  31.             Console.ReadKey();
  32.         }
  33.     }
  34. }
FeatureLayerQuery.cs
  1. using Esri.ArcGISRuntime.Data;
  2. using Esri.ArcGISRuntime.Security;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Threading.Tasks;
  6. namespace arcgisAuthTest
  7. {
  8.     /// <summary>
  9.     /// Ejecución de queries sobre un Feature Layer de Arcgis Online.
  10.     /// Incluye autenticación por medio de usuario y contraseña.
  11.     /// Author: Cristian de León R.
  12.     /// </summary>
  13.     public class FeatureLayerQuery
  14.     {
  15.         private string _password;
  16.         private Credential _credential;
  17.         public string ServiceUrl { get; private set; }
  18.         public string Usuario { get; private set; }
  19.         public ServiceFeatureTable SrvFeatureTable { get; private set; }
  20.         public FeatureLayerQuery(string serviceUrl, string usuario, string password)
  21.         {
  22.             Usuario = usuario;
  23.             _password = password;
  24.             ServiceUrl = serviceUrl;
  25.             _credential = CreateKnownCredentials(new CredentialRequestInfo()
  26.             {
  27.                 AuthenticationType = AuthenticationType.Token,
  28.                 ServiceUri = new Uri(ServiceUrl)
  29.             }).GetAwaiter().GetResult();
  30.         }
  31.         /// <summary>
  32.         /// Ejecutar una consulta contra un Feature Layer de Arcgis Online.
  33.         /// </summary>
  34.         /// <param name="whereClause">Ej.: "upper(placa_motocicleta) LIKE '%" + (NumPlaca.Trim().ToUpper()) + "%'"</param>
  35.         /// <param name="outFields">listado de campos a retornar o * para devolver todos</param>
  36.         /// <param name="maxFeatures">Número máximo de features devuelto</param>
  37.         /// <returns></returns>
  38.         public List<Feature> QueryFeature(string whereClause, List<string> outFields, int maxFeatures = 0)
  39.         {
  40.             Uri serviceUri = new Uri(ServiceUrl);
  41.             SrvFeatureTable = new ServiceFeatureTable(serviceUri);
  42.             SrvFeatureTable.Credential = _credential;
  43.             // Create a query parameters that will be used to Query the feature table
  44.             var queryParams = new QueryParameters()
  45.             {
  46.                 WhereClause = whereClause,
  47.                 MaxFeatures = maxFeatures
  48.             };
  49.             // Query the feature table
  50.             FeatureQueryResult queryResult = SrvFeatureTable.PopulateFromServiceAsync(queryParams, true, outFields).GetAwaiter().GetResult();
  51.             List<Feature> features = new List<Feature>();
  52.             foreach (Feature r in queryResult)
  53.             {
  54.                 features.Add(r);
  55.                 //Console.WriteLine(r.Attributes["placa_motocicleta"]);
  56.             }
  57.             return features;
  58.         }
  59.         /// <summary>
  60.         /// Challenge method that checks for service access with known (hard coded) credentials
  61.         /// </summary>
  62.         /// <param name="info"></param>
  63.         /// <returns></returns>
  64.         private async Task<Credential> CreateKnownCredentials(CredentialRequestInfo info)
  65.         {
  66.             Credential knownCredential = null;
  67.             if (info.ServiceUri.AbsoluteUri.ToLower().Contains("arcgis.com"))
  68.             {
  69.                 knownCredential = await AuthenticationManager.Current.GenerateCredentialAsync(
  70.                     info.ServiceUri,
  71.                     Usuario,
  72.                     _password,
  73.                     info.GenerateTokenOptions);
  74.             }
  75.             return knownCredential;
  76.         }
  77.     }
  78. }

Arcgis API Reference

lunes, 4 de julio de 2016

Color Images to Bitonal (BinarizeBradley Algorithm)

Recientemente tuve que implementar una algoritmo para convertir imágenes en colores a blanco y negro (bitonal). El framework AForge.net tiene una librería muy completa para procesamiento de imágenes, sin embargo, está bajo licencia LGPLv3 y no siempre podemos hacer uso de ella en todos los proyectos.

Dejo un enlace a mi propia implementación del algoritmo BinarizeBradley bajo licencia MIT.

El código es una implementación en C# del algoritmo descrito en el siguiente Paper: Adaptive Thresholding Using the Integral Image by Derek Bradley and Gerhard Roth.

Algunas imágenes de prueba:





miércoles, 11 de febrero de 2015

Consultas jerárquicas ordenadas (T-SQL)

Los datos jerárquicos usualmente se almacenan en tablas que contienen referencias recursivas como se muestra en la imagen:


Esta estructura permite almacenar datos jerárquicos, por ejemplo, como han derivado unas distribuciones GNU/Linux de otras.

Id Nombre IdPadre
1 Debian NULL
2 Knoppix 1
3 Ubuntu 1
4 Lindows 1
5 Corel 1
6 Damn Small Linux 2
7 KnoppMyth 2
8 Mint 3
9 Kubuntu 3
10 Xubuntu 3
11 Ulteo 9
12 Redhat NULL
13 Mandrake 12

Si queremos consultar todos los registros de distribuciones basadas en Debian, podemos recurrir a una consulta utilizando la sintaxis CTE (Common Table Expression, SQL Server 2005+) como sigue:

WITH cte
     AS (SELECT A.id, A.idpadre, A.nombre
         FROM   tabla1 A
         WHERE  A.id = 1 --Iniciar con Debian
         UNION ALL
         SELECT B.id, B.idpadre, B.nombre
         FROM   tabla1 B
                INNER JOIN cte C ON B.idpadre = C.id
         WHERE  B.idpadre IS NOT NULL)
SELECT *
FROM   cte 


Esta consulta devolverá los datos en el orden en que los va recuperando. Si quisiéramos que los datos aparezcan ordenados según la jerarquía, deberemos considerar una columna adicional en la consulta que corresponda a la ruta completa desde el registro raíz (Debian) hasta cada registro.

WITH cte
     AS (SELECT A.id, A.idpadre, A.nombre,
                Cast(A.id AS VARCHAR) ruta
         FROM   tabla1 A
         WHERE  A.id = 1 --Iniciar con Debian
         UNION ALL
         SELECT B.id, B.idpadre, B.nombre,
                Cast(C.ruta + '-' + Cast(B.id AS VARCHAR) AS VARCHAR)
         FROM   tabla1 B
                INNER JOIN cte C ON B.idpadre = C.id
         WHERE  B.idpadre IS NOT NULL)
SELECT *
FROM   cte
ORDER  BY ruta ASC; 


La columna Ruta actuará como un "acumulador" de todos los IDs que han de considerarse para llegar desde el registro raíz (Debian) hasta el registro destino.

En el ejemplo, la ruta correspondiente a Ulteo es 1-3-9-11.


miércoles, 21 de enero de 2015

Variables persistentes (.NET)

Desarrollando una pequeña aplicación (Windows Service), me topé con la necesidad de mantener una variable de conteo cuyo valor fuera persistente aún después de reiniciar el servicio.

Si no se dispone de una base de datos o su uso es inapropiado (requiere crear en la base de datos una nueva tabla o campo que no deseamos), se puede pensar en almacenar la información en archivos de texto. En ambos casos es requerido agregar otras librerías al programa e implementar los métodos necesarios para gestionar el almacenamiento y recuperación del dato.

Una buena alternativa es el uso de los user-settings de la aplicación, es posible utilizarlos como variables en tiempo de ejecución y preservar su valor aún después de reiniciar el servicio o aplicación.

Para crear una variable de este tipo, se puede acceder a las propiedades del proyecto desde el Solution Explorer (Visual Studio). Posteriormente, en el apartado de Settings, se agrega una entrada especificando el nombre de la variable, el tipo y el ambito (establecido a nivel de usuario).




















El siguiente código muestra cómo manipular esta variable:

 //Establecer un valor  
 Properties.Settings.Default.ItemCounter = anyIntValue;  
 //Recuperar un valor  
 anyIntValue = Properties.Settings.Default.ItemCounter;  
 //Almacenar el valor de forma persistente  
 Properties.Settings.Default.Save();  


Saludos!