Select to view content in your preferred language

Draw and save graphics to a local file.

3271
10
08-03-2013 08:57 AM
ZachLiu1
Deactivated User
I am working on a project which includes a draw toolbar. However, I need to extend the tool to have the ability to save the graphics to a local file(text or csv), and the user can also open the file and display the stored graphics on the map.

So basically, I need to replicate the draw widget in Flex API.

What I am thinking now is when user click the save button, the JS code will store the graphics in a object. But I don't know how JavaScript can write the variable to a local file and also read it later, or if I need a server side script for the job.

Can anyone give some clues?
0 Kudos
10 Replies
AdrianMarsden
Honored Contributor
Hi

I have code that writes all stuff created with the draw toolbar to HTML5 local storage, and then reads it back next time a user uses the site.  Is that the sort of thing you are after?  As mine is for an internal site I have control over browser versions, so this works OK.

ACM
0 Kudos
ZhiqiangLiu
Emerging Contributor
It will be a great point to start, though what I need is the ability to save the graphics to a external file that can be shared among users. would you share the code?:)
0 Kudos
AdrianMarsden
Honored Contributor
It will be a great point to start, though what I need is the ability to save the graphics to a external file that can be shared among users. would you share the code?:)


OK - here goes - I have this function that is mainly copied from elsewhere

function addToMap(geometry) {
    //drawToolbar.deactivate();
    a = dijit.byId("tslider").value;
    c = hexToRgb(graphicColour, a);
    fullcolour = new dojo.Color(c);


   // fillStyle = dijit.byId('fillSelect').attr('value');
    var psize = dijit.byId("font.size").value;
    psize = psize.replace("pt", "");
    switch (geometry.type) {
    case "point":
        var symbol = new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, psize, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([255, 0, 0]), 1), new dojo.Color(graphicColour));
        if (drawtype == 'Text') {
            fullcolour = new dojo.Color([0, 0, 0, 255]);
            var font = new esri.symbol.Font(dijit.byId("font.size").value, esri.symbol.Font.STYLE_NORMAL, esri.symbol.Font.VARIANT_NORMAL, esri.symbol.Font.WEIGHT_NORMAL, "verdana");
            var symbol = new esri.symbol.TextSymbol(dijit.byId("usertext").value, font, fullcolour).setAngle(0).setOffset(0, 0).setAlign(esri.symbol.TextSymbol.ALIGN_START).setDecoration(esri.symbol.TextSymbol.DECORATION_NONE).setRotated(false).setKerning(true);


        }
        break;
    case "polyline":
        var symbol = new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, fullcolour, dijit.byId("line.size").value);
        break;
    case "polygon":
        var symbol = new esri.symbol.SimpleFillSymbol(dijit.byId('fillSelect').attr('value'), new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new dojo.Color([0, 0, 0]), dijit.byId("line.size").value), fullcolour);
        
        break;
    }
    console.debug(symbol)
    var graphic = new esri.Graphic(geometry, symbol);
    // store to local storage  graphic added 
    var now = new Date();
    var n = now.getTime();
    GraphicName = "storedGraphic" + n;
    window.localStorage.setItem(GraphicName, dojo.toJson(graphic.toJson()));
    graphic.setAttributes({
        newID: GraphicName
    });
    userGraphics.add(graphic);
    //enableID();
}


The important bits are to the end - I generate a unique name for this stored graphic, based on the time.  Then save it to local storage.
Initially I also deactivated the draw tools and reactivated the ID tool - but as you see I've changed my mind on this - these only get changed when the user closes the floating pane I have the tools in.

I then have this, that re-loads it all next session

  //graphics stored in local srtorage and display it.
    for (var i = 0; i < localStorage.length; i++) {
        var n = "SavedGraphic" + i;
        var key = localStorage.key(i);
        //we use local storage for other things, so only read in storedgraphics
        if (Left(key, 8) == 'storedGr') {
            saved = dojo.fromJson(localStorage.getItem(localStorage.key(i)));
            var graphic = new esri.Graphic(saved);
            graphic.setAttributes({
                "ID": n,
                "LSNo": i
            });
            userGraphics.add(graphic);
        }
    }


userGraphics is a graphics layer just for the, well, user graphics! - this means other stuff the system adds, like markers, and highlights, don't get mixed up.

Finally the delete from local storage, when a user deletes the graphics.  This needs this added to the create toolbar event thingy

function createToolbarAndContextMenu(map) {
    map.enableSnapping({
        snapKey: dojo.keys.copyKey
    });
    // Create and setup editing tools
    editToolbar = new esri.toolbars.Edit(map);
    dojo.connect(editToolbar, "onDeactivate", function(tool, graphic, info) {
        //we need to remove the version in local storage as we will be saving a new version 
        //if Graphics has newID it has been added this session
        //If not it has been loaded from localstorage 
        //if from LS the attribute LSNo will equal the index no in localstorage
        if (graphic.attributes.newID) {
            localStorage.removeItem(graphic.attributes.newID);
        } else {
            localStorage.removeItem(localStorage.key(graphic.attributes.LSNo));
        }
        //save a new version with name time stamped so it can be referred to in future edits/deletes this session
        var now = new Date();
        var n = now.getTime();
        GraphicName = "storedGraphic" + n;
        window.localStorage.setItem(GraphicName, dojo.toJson(graphic.toJson()));
    });
    dojo.connect(map, "onClick", function(evt) {
        editToolbar.deactivate();
    });
    createMapMenu();
    createGraphicsMenu();
}


I think the comments say enough - basically we need a common naming system for the graphics so they can be deleted.

Finally, as I use the right click context menu form an example, I alter the delete part of that.

function createGraphicsMenu() {
    // Creates right-click context menu for GRAPHICS
    ctxMenuForGraphics = new dijit.Menu({});
    ctxMenuForGraphics.addChild(new dijit.MenuItem({
        label: "Edit",
        onClick: function() {
            if (selected.geometry.type !== "point") {
                editToolbar.activate(esri.toolbars.Edit.EDIT_VERTICES, selected);
            } else {
                alert("Not implemented");
            }
        }
    }));
    ctxMenuForGraphics.addChild(new dijit.MenuItem({
        label: "Move",
        onClick: function() {
            editToolbar.activate(esri.toolbars.Edit.MOVE, selected);
        }
    }));
    ctxMenuForGraphics.addChild(new dijit.MenuItem({
        label: "Rotate/Scale",
        onClick: function() {
            if (selected.geometry.type !== "point") {
                editToolbar.activate(esri.toolbars.Edit.ROTATE | esri.toolbars.Edit.SCALE, selected);
            } else {
                alert("Not implemented");
            }
        }
    }));
    ctxMenuForGraphics.addChild(new dijit.MenuSeparator());
    ctxMenuForGraphics.addChild(new dijit.MenuItem({
        label: "Delete",
        onClick: function() {
            userGraphics.remove(selected);
            if (selected.attributes.newID) {
                localStorage.removeItem(selected.attributes.newID);
            } else {
                localStorage.removeItem(localStorage.key(selected.attributes.LSNo));
            }
        }
    }));
    ctxMenuForGraphics.startup();
    dojo.connect(userGraphics, "onMouseOver", function(evt) {
        // We'll use this "selected" graphic to enable editing tools
        // on this graphic when the user click on one of the tools
        // listed in the menu.
        selected = evt.graphic;
        // Let's bind to the graphic underneath the mouse cursor           
        ctxMenuForGraphics.bindDomNode(evt.graphic.getDojoShape().getNode());
    });
    dojo.connect(userGraphics, "onMouseOut", function(evt) {
        ctxMenuForGraphics.unBindDomNode(evt.graphic.getDojoShape().getNode());
    });
}



Looking at it now, I am not too sure how it all works!! It was a few months ago I did it and my memory isn't as good as it used to be.

So please take it as it is.

Cheers

ACM
0 Kudos
JeffJacobson
Frequent Contributor
I have some code that will load CSV files into point graphics layers.

The code is on GitHub here. You can test it here.  Known issues are listed here. (If you find any more issues you can add them to the list.)
0 Kudos
ZachLiu1
Deactivated User
Thanks for all these responses, they are all great help.

The only question I have right now is if there is a way I can export graphics to a local file. Based on what I learned so far this can not be done by JavaScript alone.
0 Kudos
TerryGiles
Frequent Contributor
Based on what I learned so far this can not be done by JavaScript alone.


That's pretty much true.  If your users are using IE it can be done through ActiveX but it doesn't work at all in other browsers.  Below is some old code I have that loops through the graphics on a map and dumps some attributes into a text file, which then gets opened in Word.  The user is prompted a few times along the way & if I recall we had to change some security settings in IE too.

Otherwise I think you'll need  to send the graphics (or JSON created from them) back to a web service that would prompt the user to save a text file.


    function exportToWord() {
        //exports the addresses of the parcels in the graphics file to a text file
        //on the users desktop and then opens it with the Word label template.


        if (!dojo.isIE) {
          alert("Exporting to Word only works with Internet Explorer");
          return false;
        }
        
        //note this will give them a warning about ActiveX object running
        var wshshell = new ActiveXObject("wscript.shell");
        var strDesktop = wshshell.SpecialFolders("Desktop");
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        var bolOwner = false;
        
        //text file for property locations
        fProps = fso.CreateTextFile(strDesktop + "\\plist.txt", true);
        fProps.WriteLine("NAME;ADDRESS;ZIP");
        
         var graphic;
        for (var i = 0, il = map.graphics.graphics.length; i < il; i++) {
          graphic = map.graphics.graphics;
          if (graphic.attributes) {
              fProps.WriteLine(graphic.attributes["OWNER1"] + ";" + graphic.attributes["ADDRESS1"] + ";" + graphic.attributes["ZIPCODE"]);
   }
        }

        fProps.Close();

        //now open the word label template which contains VBA to dump the olist.txt into a new doc
        var w = new ActiveXObject('Word.Application');
        w.Visible = true;
        w.Documents.Open("C:\\TEMP\\Plabeltest.docm");

 }

0 Kudos
AdrianMarsden
Honored Contributor
maybe some hints here

http://stackoverflow.com/questions/4940586/can-you-use-html5-local-storage-to-store-a-file-if-not-ho...

writing local storage to text file using the File API
0 Kudos
ZachLiu1
Deactivated User
Ok, Half of the work done. My solution is using a list to store graphics drawn as JSON strings and send the whole list to a server script and ask users for download.

Here is the function I use, and $.generateFile is a function I borrowed which constructs a hidden form and send the content through submission. Not sure if I can use ajax call to do it directly though.

function exportGraphics(){
                var graphicList = [];
                $.each(map.graphics.graphics, function(index, graphic){
                    var graphicStr = JSON.stringify(graphic.toJson());
                    graphicList.push(graphicStr);
                });

                $.generateFile({
                    filename : 'graphics.txt',
                    content :  graphicList,
                    script : 'download.php'
                });
            }


The next step will be read the txt file from user input and display the graphics stored in it.
I guess I still need the user to upload the file to the server and read it back using JavaScript.
0 Kudos
TimCollyer
Regular Contributor
"Not sure if I can use ajax call to do it directly though"

Yes, I believe you can do this using dojo/request/iframe (I presume jQuery would have something similar if that is what you are using) without having to make the hidden form.

I use code similar to this (although the code below is untested)...

function exportData() {
    require(["dojo/json", "dojo/_base/array", "dojo/request/iframe"], function(JSON, array, iframe){
        
        var exportData = [];
        array.forEach(map.graphics.graphics, function(g) {
            exportData.push(g);
        });
        
        var jsonString = JSON.stringify(exportData);
        iframe.post("<url to your server script>", {
            data : {
                records : jsonString
            },
            timeout : 10000
        });        
    });
}

0 Kudos