ESRI JavaScript API: Datei Upload und Anzeige in der Karte

Wie man über die ESRI JavaScript API in einer .NET ASPX Seite einen Datei-Upload einer XML Datei mit Punktdaten realisiert und die Daten auf dem GraphicsLayer in der Karte anzeigt. …

In der JS-API kann man nicht ohne weiteres lokale Daten zur Anzeige in der Karte hochladen. Die Sicherheitseinstellungen erlauben es den Webbrowsern nicht, auf lokale Daten zuzugreifen. Daher braucht man für die Anzeige lokaler Daten einen Server Roundtrip, der den Upload der Dateien bekommt, verwaltet und wieder dem Klienten zurückgibt. Da die JS-API allein im Webbrowser läuft ist die Kommunikation zwischen Serverbackend und JavaScript Frotend die kniffelige Herausforderung.

Ausgangslage war ein XML, dass vom lokalen Rechner hochgeladen werden sollte, Punktdaten mit Attributen enthält und in dem JS-API Klienten auf die Karte gezeichnet werden sollte.

Ich habe mich für die folgende Variante entschieden: Der JS-API Klient wird in eine .NET ASPX Seite eingebettet. In dieser ASPX Seite wird der Upload realisiert. Das C# Backend kümmert sich um die Verwaltung der Uploads. Da der CodeBehind multi-user fähig ist, sollte der gleichzeitige Upload von mehreren Personen funktionieren, ohne das Person A auf einmal die Ergebnisse von Person B sieht. Im CodeBehind wird das XML geparst und in ein JSON FeatureSet (eine JS-API Struktur im JSON Format, die leicht auf dem GraphicsLayer gezeichnet werden kann) umgewandelt.

Die Schritte kurz:

  1. Erstellen einer ASPX Seite (.NET 3.5). Dort wird der JS-API Klient eingebaut.
  2. Form Element erstellen (runat="server") und einen asp:ScriptManager einfügen.
  3. Ein AsyncFileUpload Control einfügen. Das stammt aus dem AJAX Control Toolkit, dass bei der Installation von ArcGIS Server 10 mit auf dem Rechner liegt. Das AsyncFileUpload Control verhält sich etwas eigensinnig, vermeidet aber einen kompletten Seiten-Refresh nach dem Upload. Das normale FileUpload Control von .NET/ASPX hat diese Fähigkeit nicht. Dort muß man den Page-Refresh, der ja auch den JS-API Klienten und die Karte jedesmal neuladen würde, mit andern Mitteln umgehen.
  4. Einfügen einer mehzeiligen asp:TextBox. Diese kann per JavaScript oder CSS auf nicht sichtbar geschaltet werden (dojo.byId("TextBox1").style.display = 'none';), nicht aber über das APS Attribut visible. Diese Textbox dient als Container, um das JSON Ergebnis aus dem CodeBehind wieder zum Klienten zu bekommen.
  5. Einfügen einer dijit.formButton mit dem onClick Event: showOnMap(). Erläuterung zu dieser Methode später.
  6. CodeBehind in der Default.aspx.cs: Die AsyncFileUpload1_UploadedComplete Methode muß nun mit Logik gefüllt werden. Ich nehme den Inhalt meiner hochgeladenen XML Datei, parse diesen und wandle ihn in ein JSON Format um. Diese JSON Ausgabe sieht wie ein FeatureSet aus, dass z.B. bei der Query auf einen Layer entsteht.
    Um diesen JSON String wieder zum Frontend, d.h. auf die Webbrowserseite mit meinem JS-API Code zu bekommen, brauche ich einen ASP/AJAX Befehl von .NET:

     ScriptManager.RegisterClientScriptBlock(AsyncFileUpload1, AsyncFileUpload1.GetType(), "text", "top.document.getElementById('TextBox1').value='" + json + "';",
      true);
    
  7. Dieser Code füllt meine TextBox mit dem JSON Inhalten. Vorsicht: Die Textbox versteht Zeilenumbrüche im Format "rn" oder System.Environment.NewLine nicht. Ich habe versucht den JSON-String direkt an eine JS-API Methode zu überführen, dort wurden aber dann NullPointers geworfen, wenn ich auf die Map oder den GraphicsLayer zugreifen wollte. Vielleicht macht der Lebenszyklus der ASPX-Seite dort Schwierigkeiten.
  8. Die Methode showOnMap() hinter dem dijit.form.Button (siehe Punkt 5) nimmt sich nun die Inhalte aus der Textbox (dojo.byId("TextBox1")) als JSON FeatureSet und füllt über eine for-Schleife den GraphicsLayer mit diesen Grafik-Objekten.

Im Detail macht die Methode:

 function showOnMap() {
    var tb = dojo.byId("TextBox1");
    var featureSet = new esri.tasks.FeatureSet(dojo.fromJson(tb.value));
    addFeatureSetToMap(featureSet);
}
function addFeatureSetToMap(featureSet) {
  var symbol = new esri.symbol.SimpleMarkerSymbol();
  symbol.setColor(new dojo.Color([0, 0, 255]));

  var infoTemplate = new esri.InfoTemplate("${id}", "$*");

  var infoTemplate = new esri.InfoTemplate("Object", content);

  //Add objects to the graphics layer
  var url;
  dojo.forEach(featureSet.features, function (feature) {
    // EASY: graphicsLayer.add(feature.setSymbol(symbol).setInfoTemplate(infoTemplate));
    url = "./symbols/" + feature.attributes.graphic_id + ".png";
    graphicsLayer.add(feature.setSymbol(new esri.symbol.PictureMarkerSymbol(url, 25, 25)).setInfoTemplate(infoTemplate));
  });
}
 <body>
    [html:div id="mainWindow" dojotype="dijit.layout.BorderContainer" design="headline" gutters="false"
        style="width: 100%; height: 100%;"]
        <div id="header" dojotype="dijit.layout.ContentPane" region="top">
            Upload Viewer
            <div id="subheader">
                powered by ESRI JavaScript API 2.1</div>
        </div>
        <div dojotype="dijit.layout.ContentPane" id="leftPane" region="left">
            <div dojotype="dijit.layout.TabContainer">
                <div dojotype="dijit.layout.ContentPane" title="Legend" selected="true">
                    <form id="form1" runat="server">
                    <asp:ScriptManager ID="ScriptManager1" runat="server">
                    </asp:ScriptManager>
                    <div>
                        Just some time to make sure that the page doesn't get reloaded after uploading:
                        <%=DateTime.Now %>
                        <br />
                        <cc1:AsyncFileUpload ID="AsyncFileUpload1" runat="server" ThrobberID="img1" OnUploadedComplete="AsyncFileUpload1_UploadedComplete"
                            Width="300px" />
                        <asp:Image ID="img1" runat="server" Height="25px" ImageUrl="~/images/spin2.png" Width="25px" />
                        <br />
                        <asp:TextBox ID="TextBox1" runat="server" Height="300px" ReadOnly="False" TextMode="MultiLine"
                            Width="300px"></asp:TextBox>
                        <div dojotype="dijit.form.Button" id="bt1" onclick="showOnMap();">
                            Zeige auf Karte</div>
                    </div>
                    </form>
                </div>
                <div dojotype="dijit.layout.ContentPane" title="Tools">
                </div>
            </div>
        </div>
        <div id="map" dojotype="dijit.layout.ContentPane" region="center">
        </div>
        <div id="footer" dojotype="dijit.layout.ContentPane" region="bottom">
            this is the footer section
        </div>
    </div>
</body>

Die Default.aspx.cs:

 protected void AsyncFileUpload1_UploadedComplete(object sender, EventArgs e)
    {
        AsyncFileUpload asyncFileUpload = sender as AsyncFileUpload;

        if (asyncFileUpload.ContentType.Equals("text/xml"))
        {
            Stream fs = asyncFileUpload.FileContent;
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.Load(fs);
            string json = Xml2Json(xmlDocument);
            fs.Close();

            ScriptManager.RegisterClientScriptBlock(AsyncFileUpload1, AsyncFileUpload1.GetType(), "text",
                "top.document.getElementById('TextBox1').value='" + json + "';",
                true);
        }
        else
        {
            ScriptManager.RegisterClientScriptBlock(AsyncFileUpload1, AsyncFileUpload1.GetType(), "text",
                "top.document.getElementById('TextBox1').value='File is not XML.';",
                true);
        }

    }

Die Methode XML2Json(xmlDocument) parst das XML und wandelt es in JSON um. Hier passiert viel Logik, die speziell auf das XML-Format bezogen ist.

Die Ergebnisse werden im GraphicsLayer gezeichnet, eignet sich also gut für Punkte, weniger für Linien und kaum für Polygone. Beim InternetExplorer ist nach einigen 100 Punkten schon die Performance schlecht, bei mehr (Stütz)Punkten kommt irgendwann eine Meldung, ob der Benutzer den JavaScript Code abbrechen will. Firefox ist in der Performance besser und macht nach wenigen 1000 Punkten schlapp. Der Chrome Browser ist hier schnell und schafft es auch viele 1000 Punkte performant anzuzeigen.

Für komplexere Geometrien muß eine andere Lösung gefunden werden. Da sollte das Rendering nicht im Klienten auf dem GraphicsLayer erfolgen, sondern über den Server geschehen. Das erfordert aber eine komplett andere Struktur und ein Sessionmanagement.

Über einen Geoprocessing Task könnte man eine gute asynchrone Kommunikation zwischen API und Server herstellen, er würde aber wahrscheinlich auch nur ein JSON Objekt zurückgeben, dass im GraphicsLayer gezeichnet werden müsste.

Anpassung für deutsche Betriebssysteme: Je nachdem müssen folgende Zeilen hinzugefügt werden.

In der web.config (im system.web Element):

<globalization culture="en-GB" />

In der ASPX/HTML Seite zur JS-API:

var djConfig = { locale: "en" };

System: ArcGIS Server 10, JavaScript API 2.1, ASPX.NET 3.5.
.

Advertisements