Předvčerejší povídání Štěpána Bechynského o využití Virtual Earth mne inspirovalo k tomu, že jsem do kalendáře na akce.altairis.cz dopisoval podporu map. Celá komunikace je založena na zeměpisných souřadnicích WGS84 a provázanosti s GPS. Proto jsem napsal server control pro uživatelsky přívětivé zobrazování GPS souřadnic.

GPS souřadnice udávají zeměpisnou šířku (latitude) a délku (longitude) ve stupních. Principiálně se tedy jedná o dvě desetinná čísla v rozsahu (-180, 180). Souřadnice sídla českého Microsoftu lze tedy zapsat jako lat 50.047506, long 14.454411.

V zájmu zmatení uživatelů a programů se ale často používá též zápis ve stupních a minutách, případně stupních, minutách a vteřinách. Navíc se obvykle nepoužívají záporná čísla, ale uvádí se, zda se jedná o serverní (N, lat > 0) nebo jižní (S, lat < 0) šířku a východní (E, long > 0) nebo západní (W, long < 0) délku. Tentýž údaj lze tedy zapsat jako:

Interně data ukládám samozřejmě jako desetinné číslo (typ decimal) a chci je zobrazovat deklarativně pomocí data bindingu. Řešení je jednoduché: napsat si vlastní server control, který se o to postará.

GpsCoordinatesLabel

Prvek GpsCoordinatesLabel se používá takto:

<%@Register TagPrefix="altairis" Namespace="Altairis.Web.UI.WebControls" %>

<altairis:GpsCoordinatesLabel ID="GpsCoordinatesLabel1" runat="server"

    Latitude="50,047506"

    Longitude="14,454411"

    Format="DegMinSec"

    InvalidText="neznámé" />

Má následující vlastnosti:

Zdrojový kód třády Altairis.Web.UI.WebControls.GpsCoordinatesLabel je následující: 

using System;

using System.Text;

using System.ComponentModel;

 

namespace Altairis.Web.UI.WebControls {

    public enum GpsCoordinatesFormat {

        Deg, DegMin, DegMinSec

    }

 

    public class GpsCoordinatesLabel : System.Web.UI.WebControls.WebControl {

        private decimal latitude, longitude;

        private GpsCoordinatesFormat format = GpsCoordinatesFormat.DegMinSec;

        private string invalidText;

 

        [Category("Beahvior"), DefaultValue(GpsCoordinatesFormat.DegMinSec), Description("Format of coordinates")]

        public GpsCoordinatesFormat Format {

            get { return format; }

            set { format = value; }

        }

 

        [Bindable(true), Description("Geographic longitude")]

        public string Longitude {

            get { return longitude.ToString(); }

            set {

                try { longitude = Convert.ToDecimal(value); }

                catch { longitude = decimal.MinValue; }

            }

        }

 

        [Bindable(true), Description("Geographic latitude")]

        public string Latitude {

            get { return latitude.ToString(); }

            set {

                try { latitude = Convert.ToDecimal(value); }

                catch { latitude = decimal.MinValue; }

            }

        }

 

        [Bindable(true), Description("Text to display if supplied coordinates are invalid")]

        public string InvalidText {

            get { return invalidText; }

            set { invalidText = value; }

        }

 

        protected override void RenderContents(System.Web.UI.HtmlTextWriter writer) {

            if (this.latitude == decimal.MinValue || this.longitude == decimal.MinValue) {

                writer.Write(this.InvalidText);

            }

            else {

                writer.Write("{0}{1} {2}{3}",

                    FormatDMS(this.latitude), this.latitude > 0 ? 'N' : 'S',

                    FormatDMS(this.longitude), this.longitude > 0 ? 'E' : 'W');

            }

        }

 

        private string FormatDMS(decimal d) {

            d = Math.Abs(d);

            decimal m = (d - Math.Floor(d)) * 60;

            decimal s = (m - Math.Floor(m)) * 60;

 

            switch (this.Format) {

                case GpsCoordinatesFormat.DegMinSec:

                    return string.Format(new System.Globalization.CultureInfo("en-US"), "{0}°{1}'{2:N3}\"", Math.Floor(d), Math.Floor(m), s);

                case GpsCoordinatesFormat.DegMin:

                    return string.Format(new System.Globalization.CultureInfo("en-US"), "{0}°{1:N6}'", Math.Floor(d), m);

                default:

                    return string.Format(new System.Globalization.CultureInfo("en-US"), "{0:N6}°", d);

            }

 

        }

    }

}

Vlastní přepočet je jednoduchý a provádí ho metoda FormatDMS. Většinu třídy tvoří infrastruktura, jejímž raison d'etre je aby se komponenta chovala slušně i když na vstupu dostane nesmyslná data.

Morální ponaučení

Síla technologie ASP.NET leží především v modularitě, opakované použitelnosti jednotlivých modulů a jednoduché, ale chytře vymyšlené infrastruktuře, která moduly propojuje dohromady. Pokud vám vestavěná funkčnost zcela nevyhovuje, je obvykle možné snadno ji modifikovat nebo si napsat vlastní modul, který bez problémů napojíte na existující kód.

Tvorba jednoduchých serverových ovládacích prvků je způsobem, jak využít sílu deklarativního data bindingu s minimální námahou. Pokud ve své aplikaci používáte speciální nebo podmíněné formátování dat, není dobrý nápad řešit ho na aplikační (nebo dokonce až datové) vrstvě, ani se nemusíte vzdávat komfortního deklarativního způsobu práce. Do serverového ovládacího prvku mlůžete napsat i poměrně pokročilou logiku formátování a zobrazování informací.